Nie do końca rozumiem różnicę między Task.Wait
i await
.
Mam coś podobnego do następujących funkcji w usłudze WebAPI ASP.NET:
public class TestController : ApiController
{
public static async Task<string> Foo()
{
await Task.Delay(1).ConfigureAwait(false);
return "";
}
public async static Task<string> Bar()
{
return await Foo();
}
public async static Task<string> Ros()
{
return await Bar();
}
// GET api/test
public IEnumerable<string> Get()
{
Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());
return new string[] { "value1", "value2" }; // This will never execute
}
}
Gdzie Get
będzie impas.
Co może to powodować? Dlaczego nie powoduje to problemów, gdy używam oczekiwania blokującego zamiast await Task.Delay
?
Task.Delay(1).Wait()
co jest wystarczająco dobry.Task.Delay(1).Wait()
jest w zasadzie dokładnie tym samym coThread.Sleep(1000)
. W rzeczywistym kodzie produkcyjnym rzadko jest to właściwe.WaitAll
powoduje impas. Zobacz link do mojego bloga w mojej odpowiedzi, aby uzyskać więcej informacji. Zamiast tego powinieneś użyćawait Task.WhenAll
.ConfigureAwait(false)
do pojedynczego połączenia doBar
lubRos
nie impasu, ale ponieważ masz przeliczalny, który jest tworzenie więcej niż jeden, a następnie czeka na wszystkich tych, pierwszy pasek impasu drugi. Jeśliawait Task.WhenAll
zamiast czekać na wszystkie zadania, aby nie zablokować kontekstu ASP, zobaczysz, że metoda powraca normalnie..ConfigureAwait(false)
całego drzewa w górę, aż do momentu zablokowania, w ten sposób nic nigdy nie próbuje wrócić do głównego kontekstu; to powinno działać. Inną opcją byłoby rozwinięcie wewnętrznego kontekstu synchronizacji. Link . Jeśli umieściszTask.WhenAll
w sposóbAsyncPump.Run
zostanie ona skutecznie blokować na całość bez potrzebyConfigureAwait
gdziekolwiek, ale to prawdopodobnie nadmiernie skomplikowane rozwiązanie.Odpowiedzi:
Wait
iawait
- choć podobne koncepcyjnie - są w rzeczywistości zupełnie inne.Wait
będzie synchronicznie blokować do momentu zakończenia zadania. Tak więc bieżący wątek jest dosłownie zablokowany, czekając na zakończenie zadania. Zasadniczo powinieneś używać „doasync
samego końca”; to znaczy nie blokujasync
kodu. Na moim blogu szczegółowo omawiam, w jaki sposób blokowanie kodu asynchronicznego powoduje zakleszczenie .await
będzie asynchronicznie czekać na zakończenie zadania. Oznacza to, że bieżąca metoda jest „wstrzymana” (jej stan jest przechwytywany), a metoda zwraca swojemu rozmówcy niepełne zadanie. Później, gdyawait
wyrażenie zostanie zakończone, pozostała część metody jest planowana jako kontynuacja.Wspomniałeś także o „bloku kooperacyjnym”, przez który zakładam, że masz na myśli zadanie, które
Wait
wykonujesz w oczekującym wątku. Są sytuacje, w których może się to zdarzyć, ale jest to optymalizacja. Istnieje wiele sytuacji, w których nie może się to zdarzyć, na przykład jeśli zadanie jest dla innego programu planującego lub jeśli zostało już uruchomione lub jeśli nie jest to zadanie kodowe (na przykład w twoim kodzie:Wait
nie można wykonaćDelay
zadania bezpośrednio, ponieważ nie ma kodu dla tego).Pomocne może okazać się moje
async
/await
wprowadzenie .źródło
Wait
działa dobrzeawait
impas.await Task.Delay(1)
zTask.Delay(1).Wait()
usługi działa dobrze, w przeciwnym razie zakleszczenia.Wait
blokuje wątek i nie można go używać do innych celów.await
kodem. Albo to, albo impas nie był związany z żadnym z nich, a ty źle zdiagnozowałeś problem.SynchronizationContext
blokadę w żądaniu programu ASP.NET Core, aby nie blokować się.Na podstawie tego, co przeczytałem z różnych źródeł:
await
Wyrażenie nie blokuje nić, na której jest wykonywany. Zamiast tego powoduje, że kompilator zarejestruje resztęasync
metody jako kontynuację oczekiwanego zadania. Kontrola następnie powraca do obiektu wywołującegoasync
metodę. Po zakończeniu zadania wywołuje jego kontynuację, a wykonywanieasync
metody jest wznawiane od miejsca, w którym zostało przerwane.Aby poczekać na
task
ukończenie jednego , możesz wywołać jegoTask.Wait
metodę. WywołanieWait
metody blokuje wątek wywołujący, dopóki instancja jednej klasy nie zakończy wykonywania. Metoda bez parametrówWait()
służy do bezwarunkowego oczekiwania na zakończenie zadania. Zadanie symuluje pracę, wywołującThread.Sleep
metodę uśpienia na dwie sekundy.Ten artykuł jest również dobrą lekturą.
źródło
Niektóre ważne fakty nie zostały podane w innych odpowiedziach:
„async oczekuje” jest bardziej złożony na poziomie CIL, a zatem kosztuje pamięć i czas procesora.
Każde zadanie można anulować, jeśli czas oczekiwania jest niedopuszczalny.
W przypadku „asynchronicznego oczekiwania” nie mamy modułu obsługującego takie zadanie, aby je anulować lub monitorować.
Korzystanie z zadania jest bardziej elastyczne niż „asynchroniczne czekanie”.
Każda funkcja synchronizacji może być zapakowana przez asynchronię.
„async oczekuje” generuje wiele problemów. Nie oczekujemy teraz, że instrukcja zostanie osiągnięta bez debugowania środowiska wykonawczego i kontekstu. Jeśli pierwsze oczekiwanie nie zostanie osiągnięte, wszystko jest zablokowane . Czasami zdarza się, że zdarzenie, które oczekuje, wciąż się kończy, ale wszystko jest zablokowane:
https://github.com/dotnet/runtime/issues/36063
Nie rozumiem, dlaczego muszę żyć z duplikacją kodu dla metody synchronizacji i asynchronizacji lub używania hacków.
Wniosek: Utwórz zadanie ręcznie i kontroluj je, jest znacznie lepsze. Handler to Task daje większą kontrolę. Możemy monitorować zadania i zarządzać nimi:
https://github.com/lsmolinski/MonitoredQueueBackgroundWorkItem
Przepraszam za mój angielski.
źródło