Oczekuj na ukończone zadanie takie samo jak zadanie.

117

Obecnie czytam książkę „ Współbieżność w C # Cookbook ” autorstwa Stephena Cleary'ego i zauważyłem następującą technikę:

var completedTask = await Task.WhenAny(downloadTask, timeoutTask);  
if (completedTask == timeoutTask)  
  return null;  
return await downloadTask;  

downloadTaskjest wezwaniem do httpclient.GetStringAsynci timeoutTaskwykonuje Task.Delay.

W przypadku, gdy nie upłynął limit czasu, downloadTaskjest już zakończony. Dlaczego trzeba czekać sekundę zamiast wracać downloadTask.Result, skoro zadanie zostało już wykonane?

julio.g
źródło
3
Brakuje tu trochę kontekstu i jeśli ludzie nie mają łatwo dostępu do książki, będziesz musiał ją uwzględnić. Co to jest downloadTaski timeoutTask? Co oni robią?
Mike Perrenoud
7
Nie widzę tutaj rzeczywistego sprawdzenia pomyślnego zakończenia. Zadanie może być bardzo dobrze obciążone, w takim przypadku zachowanie będzie inne ( AggregateExceptionz Resultpierwszym wyjątkiem przez ExceptionDispatchInfowith await). Bardziej szczegółowo omówione w artykule Stephena Toub „Task Exception Handling in .NET 4.5”: blogs.msdn.com/b/pfxteam/archive/2011/09/28/… )
Kirill Shlenskiy,
powinieneś uczynić to odpowiedzią @KirillShlenskiy
Carsten
@MichaelPerrenoud Masz rację, dziękuję za uwagę, edytuję pytanie.
julio.g

Odpowiedzi:

160

Jest tu już kilka dobrych odpowiedzi / komentarzy, ale tylko po to, aby zadzwonić ...

Istnieją dwa powody, dlaczego wolę awaitponad Result(lub Wait). Po pierwsze, obsługa błędów jest inna; awaitnie zawija wyjątku w AggregateException. Idealnie byłoby, gdyby kod asynchroniczny nigdy nie musiał mieć do czynienia AggregateException, chyba że specjalnie tego chce .

Drugi powód jest nieco bardziej subtelny. Jak opisuję na moim blogu (iw książce), Result/ Waitmoże powodować zakleszczenia i może powodować jeszcze bardziej subtelne zakleszczenia, gdy jest używany w asyncmetodzie . Tak więc, kiedy czytam kod i widzę Resultlub Wait, jest to natychmiastowa flaga ostrzegawcza. Znak Result/ Waitjest poprawny tylko wtedy, gdy masz absolutną pewność, że zadanie zostało już wykonane. Jest to nie tylko trudne do zobaczenia na pierwszy rzut oka (w kodzie świata rzeczywistego), ale jest też bardziej kruche w przypadku zmian w kodzie.

Nie oznacza to, że Result/ nieWait powinno się nigdy używać. Postępuję zgodnie z tymi wytycznymi w moim własnym kodzie:

  1. Kod asynchroniczny w aplikacji może używać tylko await.
  2. Asynchroniczny kod narzędziowy (w bibliotece) może czasami używać Result/, Waitjeśli kod naprawdę tego wymaga. Takie użycie prawdopodobnie powinno mieć komentarze.
  3. Równoległy kod zadania może używać Resulti Wait.

Zauważ, że (1) jest zdecydowanie częstym przypadkiem, stąd moja tendencja do używania awaitwszędzie i traktowania innych przypadków jako wyjątków od ogólnej reguły.

Stephen Cleary
źródło
W naszych projektach napotkaliśmy na impas przy użyciu „result” zamiast „await”. zepsuta część nie ma błędu kompilacji i po pewnym czasie kod staje się niestabilny.
Ahmad Mousavi
@Stephen, czy mógłbyś mi wyjaśnić, dlaczego „Idealnie byłoby, gdyby kod asynchroniczny nigdy nie miał do czynienia z AggregateException, chyba że specjalnie tego chce”
vcRobe
4
@vcRobe, ponieważ awaitzapobiega AggregateExceptionopakowaniu. AggregateExceptionzostał zaprojektowany do programowania równoległego, a nie asynchronicznego.
Stephen Cleary
2
> „Oczekiwanie jest poprawne tylko wtedy, gdy masz absolutną pewność, że zadanie zostało już wykonane”. .... Więc dlaczego to się nazywa Czekaj?
Ryan The Leach
4
@RyanTheLeach: pierwotnym celem Waitbyło przyłączenie się do instancji Dynamic Task Parallelism Task . Używanie go do oczekiwania na Taskwystąpienia asynchroniczne jest niebezpieczne. Microsoft rozważał wprowadzenie nowego typu „Obietnicy”, ale Taskzamiast tego zdecydował się użyć istniejącego ; Kompromisem z ponownego wykorzystania istniejącego Tasktypu do zadań asynchronicznych jest to, że otrzymujesz kilka interfejsów API, które po prostu nie powinny być używane w kodzie asynchronicznym.
Stephen Cleary
12

Ma to sens, jeśli timeoutTaskjest produktem, z Task.Delayktórego wierzę, że jest w książce.

Task.WhenAnyzwraca Task<Task>, gdzie wewnętrzne zadanie jest jednym z tych, które przekazałeś jako argumenty. Można to przepisać w ten sposób:

Task<Task> anyTask = Task.WhenAny(downloadTask, timeoutTask);
await anyTask;
if (anyTask.Result == timeoutTask)  
  return null;  
return downloadTask.Result; 

W obu przypadkach, ponieważ downloadTaskzostało już zakończone, istnieje niewielka różnica między return await downloadTaski return downloadTask.Result. Chodzi o to, że ta ostatnia rzuci, AggregateExceptionktóra otacza każdy oryginalny wyjątek, jak wskazał @KirillShlenskiy w komentarzach. Ten pierwszy po prostu ponownie wyrzuciłby oryginalny wyjątek.

W obu przypadkach, gdziekolwiek obsługujesz wyjątki, powinieneś sprawdzić, AggregateExceptionczy nie ma wyjątków wewnętrznych, aby znaleźć przyczynę błędu.

noseratio
źródło