Zrobiłem ten test jednostkowy i nie rozumiem, dlaczego „czekaj na Task.Delay ()” nie czeka!
[TestMethod]
public async Task SimpleTest()
{
bool isOK = false;
Task myTask = new Task(async () =>
{
Console.WriteLine("Task.BeforeDelay");
await Task.Delay(1000);
Console.WriteLine("Task.AfterDelay");
isOK = true;
Console.WriteLine("Task.Ended");
});
Console.WriteLine("Main.BeforeStart");
myTask.Start();
Console.WriteLine("Main.AfterStart");
await myTask;
Console.WriteLine("Main.AfterAwait");
Assert.IsTrue(isOK, "OK");
}
Oto wynik testu jednostkowego:
Jak to możliwe, że „czekanie” nie czeka, a główny wątek trwa?
Task.Run()
po pierwszymConsole.WriteLine
?Odpowiedzi:
new Task(async () =>
Zadanie nie wymaga
Func<Task>
, aleAction
. Wywoła metodę asynchroniczną i oczekuje, że zakończy się po powrocie. Ale tak nie jest. Zwraca zadanie. To zadanie nie jest oczekiwane przez nowe zadanie. W przypadku nowego zadania zadanie jest wykonywane po zwróceniu metody.Musisz użyć zadania, które już istnieje, zamiast owijać je w nowe zadanie:
źródło
Task.Run(async() => ... )
jest również opcjąmyTask.Start();
podniesieInvalidOperationException
myTask.Start()
dałby wyjątek dla swojej alternatywy i powinien zostać usunięty podczas używaniaTask.Run(...)
. W twoim rozwiązaniu nie ma błędu.Problem polega na tym, że używasz nie-ogólnej
Task
klasy, która nie ma na celu uzyskania wyniku. Kiedy tworzyszTask
instancję, przekazując delegata asynchronicznego:... delegat jest traktowany jako
async void
.async void
Nie jestTask
, 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życieTask<TResult>
klasy ogólnej , ponieważ chcesz zwrócić wynik, a wynik jest innyTask
. Musisz więc stworzyćTask<Task>
:Teraz, kiedy jesteś
Start
zewnętrznyTask<Task>
, zostanie on ukończony niemal natychmiast, ponieważ jego zadaniem jest po prostu stworzenie wnętrzaTask
. Będziesz musiał także poczekać na wnętrzeTask
. Oto jak można to zrobić:Masz dwie możliwości. Jeśli nie potrzebujesz wyraźnego odniesienia do wnętrza
Task
, możesz po prostuTask<Task>
dwukrotnie poczekać na zewnętrzny :... lub możesz użyć wbudowanej metody rozszerzenia,
Unwrap
która łączy zadania zewnętrzne i wewnętrzne w jedno:To rozpakowywanie odbywa się automatycznie, gdy korzystasz ze znacznie popularniejszej
Task.Run
metody, która tworzy gorące zadania, więcUnwrap
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ą typuTask<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 przynajmniejFunc<T>
argumentu (co oznacza, że przynajmniej wygenerowanyTask
nie zostanie zignorowany). W niefortunnym przypadku, że ta metoda akceptujeAction
, twój delegat będzie traktowany jakoasync void
. To rzadko jest to, czego chcesz, jeśli w ogóle.źródło
źródło
await
opóźnienie zadania nie opóźni tego zadania, prawda? Usunąłeś funkcjonalność.