Problem polega na tym, że używasz nie-ogólnej Task
klasy, która nie ma na celu uzyskania wyniku. Kiedy tworzysz Task
instancję, przekazując delegata asynchronicznego:
Task myTask = new Task(async () =>
... delegat jest traktowany jako async void
. async void
Nie jest Task
, to nie może być oczekiwany, jego wyjątek nie może być obsługiwane, i to jest źródłem tysięcy pytań złożonych przez sfrustrowanych programistów tutaj w StackOverflow i gdzie indziej. Rozwiązaniem jest użycie Task<TResult>
klasy ogólnej , ponieważ chcesz zwrócić wynik, a wynik jest inny Task
. Musisz więc stworzyć Task<Task>
:
Task<Task> myTask = new Task<Task>(async () =>
Teraz, kiedy jesteś Start
zewnętrzny Task<Task>
, zostanie on ukończony niemal natychmiast, ponieważ jego zadaniem jest po prostu stworzenie wnętrza Task
. Będziesz musiał także poczekać na wnętrze Task
. Oto jak można to zrobić:
myTask.Start();
Task myInnerTask = await myTask;
await myInnerTask;
Masz dwie możliwości. Jeśli nie potrzebujesz wyraźnego odniesienia do wnętrza Task
, możesz po prostu Task<Task>
dwukrotnie poczekać na zewnętrzny :
await await myTask;
... lub możesz użyć wbudowanej metody rozszerzenia, Unwrap
która łączy zadania zewnętrzne i wewnętrzne w jedno:
await myTask.Unwrap();
To rozpakowywanie odbywa się automatycznie, gdy korzystasz ze znacznie popularniejszej Task.Run
metody, która tworzy gorące zadania, więc Unwrap
nie jest obecnie często używana.
Jeśli zdecydujesz, że delegat asynchroniczny musi zwrócić wynik, na przykład a string
, powinieneś zadeklarować myTask
zmienną typu Task<Task<string>>
.
Uwaga: Nie popieram używania Task
konstruktorów do tworzenia zimnych zadań. Jako, że praktyka jest ogólnie niezadowolona, z powodów, których tak naprawdę nie wiem, ale prawdopodobnie dlatego, że jest używana tak rzadko, że może potencjalnie zaskoczyć innych nieświadomych użytkowników / opiekunów / recenzentów kodu.
Porady ogólne: Uważaj za każdym razem, gdy podajesz delegata asynchronicznego jako argument metody. Ta metoda powinna idealnie oczekiwać Func<Task>
argumentu (co oznacza, że rozumie delegatów asynchronicznych) lub przynajmniej Func<T>
argumentu (co oznacza, że przynajmniej wygenerowany Task
nie zostanie zignorowany). W niefortunnym przypadku, że ta metoda akceptuje Action
, twój delegat będzie traktowany jako async void
. To rzadko jest to, czego chcesz, jeśli w ogóle.