2013-07-24 7 views
5

Po raz pierwszy używam async (i .Net 4.5) po raz pierwszy i natknąłem się na coś, co mnie zaskoczyło. Nie ma zbyt wielu informacji na temat klasy VoidTaskResult, którą mogę znaleźć w sieci, więc przyszedłem tutaj, aby sprawdzić, czy ktoś ma jakieś pomysły na temat tego, co się dzieje.Co to jest typ VoidTaskResult, ponieważ odnosi się do metod asynchronicznych?

Mój kod jest podobny do poniższego. Oczywiście jest to znacznie uproszczone. Podstawową ideą jest wywoływanie metod wtyczek, które są asynchroniczne. Jeśli zwrócą Zadanie, nie ma zwracanej wartości z połączenia asynchronicznego. Jeśli zwrócą Task <>, to jest. Nie wiemy z góry, jakiego rodzaju są, więc chodzi o to, aby przyjrzeć się typowi wyniku za pomocą odbicia (IsGenericType jest prawdziwe, jeśli typ to Type <>) i uzyskać wartość za pomocą typu dynamicznego.

W moim prawdziwym kodzie wywołuję metodę wtyczek poprzez odbicie. Nie sądzę, że powinno to wpłynąć na zachowanie, które widzę.

// plugin method 
public Task yada() 
{ 
// stuff 
} 

public async void doYada() 
{ 
    Task task = yada(); 
    await task; 

    if (task.GetType().IsGenericType) 
    { 
    dynamic dynTask = task; 
    object result = dynTask.Result; 
    // do something with result 
    } 
} 

Działa to dobrze dla metody wtyczki pokazanej powyżej. IsGenericType ma wartość false (zgodnie z oczekiwaniami).

Jednak jeśli kiedykolwiek tak nieznacznie zmienić deklarację metody wtyczki, IsGenericType wraca teraz prawdziwymi i rzeczy przerwy:

public async Task yada() 
{ 
// stuff 
} 

Gdy to zrobisz, następujące jest wyjątek od wyniku linia (Object = dynTask.Result;):

RuntimeBinderException

Jeśli kopać do obiektu zadania, to rzeczywiście wydaje się być typem. VoidTaskResult to prywatny typ w przestrzeni nazw wątków z prawie niczym.

VoidTaskResult task

Próbowałem zmiany mojego Telefoniczny kod:

public async void doYada() 
{ 
    Task task = yada(); 
    await task; 

    if (task.GetType().IsGenericType) 
    { 
    object result = task.GetType().GetProperty("Result").GetMethod.Invoke(task, new object[] { }); 
    // do something with result 
    } 
} 

Ten „powiedzie” w tym sensie, że nie rzuca, ale teraz wynik jest typu „VoidTaskResult”, których nie mogę sensownie zrób cokolwiek z.

Powinienem dodać, że ciężko mi jest nawet sformułować prawdziwe pytanie na to wszystko. Być może moje prawdziwe pytanie brzmi "What is VoidTaskResult?" Lub "Dlaczego ta dziwna rzecz ma miejsce, gdy dynamicznie wzywam metodę asynchroniczną?" a może nawet "Jak wywołać metody wtyczek, które są opcjonalnie asynchroniczne?" W każdym razie, umieszczam to tam w nadziei, że jeden z guru będzie mógł rzucić trochę światła.

Odpowiedz

9

Wynika to ze sposobu, w jaki została zaprojektowana hierarchia klas wokół zadań (w szczególności źródeł ukończenia zadań).

Po pierwsze, Task<T> pochodzi z Task. Zakładam, że już to wiesz. Można również tworzyć typy Task lub Task<T> dla zadań wykonujących kod. Np. Jeśli Twój pierwszy przykład powrócił Task.Run lub coś innego, to zwróciłby rzeczywisty obiekt Task.

Problem pojawia się, gdy zastanawiasz się, w jaki sposób TaskCompletionSource<T> współdziała z hierarchią zadań.TaskCompletionSource<T> służy do tworzenia zadań, które nie uruchamiają kodu, ale raczej działają jako powiadomienie o zakończeniu niektórych operacji. Np. Limity czasu, owijki wejścia/wyjścia lub metody async.

Nie ma non-generic TaskCompletionSource typ, więc jeśli chcesz mieć zgłoszenie takiego bez zwracanej wartości (na przykład limity czasu lub async Task metodami), to trzeba stworzyć TaskCompletionSource<T> jakiegoś T i powrót Task<T>. Zespół async musiał wybrać metody T dla async Task, więc utworzyli typ VoidTaskResult.

Zazwyczaj nie stanowi to problemu. Od Task<T> pochodzi od Task, wartość jest konwertowana na Task i wszyscy są zadowoleni (w statycznym świecie). Jednak każde zadanie utworzone przez TaskCompletionSource<T> jest faktycznie typu Task<T>, a nie Task, a widzisz to z kodem odbicia/dynamicznym.

Końcowym rezultatem jest to, że musisz leczyć Task<VoidTaskResult>, tak jak to było Task. Jednakże, VoidTaskResult jest szczegółem implementacji; może się zmienić w przyszłości.

Zalecam zatem, aby opierać swoją logikę na (zadeklarowanym) typie zwracanej wartości yada, a nie na (rzeczywistej) wartości zwracanej. Dokładniej imituje to, co robi kompilator.

Task task = (Task)yadaMethod.Invoke(...); 
await task; 

if (yadaMethod.ReturnType.IsGenericType) 
{ 
    ... 
} 
+0

Dlaczego zespół asynchroniczny wprowadził funkcję VoidTaskResult, zamiast tylko wprowadzać nietypowy obiekt TaskCompletionSource? – Puppy

+0

@Puppy: Podejrzewam, że było o wiele mniej pracy. Napisanie nieinwazyjnego 'TaskCompletionSource' nie jest zbyt trudne, ale każdy publiczny API musi przejść przez garść innych" bramek ", zanim będzie mógł zostać zwolniony. Recenzje zabezpieczeń itp. –