Warten Sie auf eine abgeschlossene Aufgabe wie auf task.Result?

117

Ich lese gerade " Concurrency in C # Cookbook " von Stephen Cleary und habe die folgende Technik bemerkt:

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

downloadTaskist ein Aufruf an httpclient.GetStringAsyncund timeoutTaskwird ausgeführt Task.Delay.

Falls es keine Zeitüberschreitung gab, downloadTaskist es bereits abgeschlossen. Warum muss eine Sekunde gewartet werden, anstatt zurückzukehren downloadTask.Result, da die Aufgabe bereits abgeschlossen ist?

julio.g
quelle
3
Hier fehlt ein bisschen Kontext, und wenn die Leute nicht leicht auf das Buch zugreifen können, müssen Sie es einschließen. Was ist downloadTaskund timeoutTask? Was machen Sie?
Mike Perrenoud
7
Ich sehe hier keine tatsächliche Überprüfung für den erfolgreichen Abschluss. Die Aufgabe konnte sehr gut bemängelt werden, und in diesem Fall ist das Verhalten wird anders sein ( AggregateExceptionmit Resultvs erster Ausnahme über ExceptionDispatchInfomit await). Ausführlicher besprochen in Stephen Toubs "Task Exception Handling in .NET 4.5": blogs.msdn.com/b/pfxteam/archive/2011/09/28/… )
Kirill Shlenskiy
Sie sollten dies eine Antwort machen @KirillShlenskiy
Carsten
@ MichaelPerrenoud Du hast Recht, danke, dass du es bemerkt hast. Ich werde die Frage bearbeiten.
Uhr

Antworten:

160

Es gibt hier bereits einige gute Antworten / Kommentare, aber nur um ...

Es gibt zwei Gründe , warum ich es vorziehen , awaitüber Result(oder Wait). Das erste ist, dass die Fehlerbehandlung unterschiedlich ist; awaitschließt die Ausnahme nicht in ein AggregateException. Im Idealfall sollte asynchroner Code niemals behandelt werden müssen AggregateException, es sei denn, er möchte dies ausdrücklich .

Der zweite Grund ist etwas subtiler. Wie ich in meinem Blog (und im Buch) beschreibe, kann Result/ WaitDeadlocks verursachen und bei Verwendung in einer asyncMethode noch subtilere Deadlocks verursachen . Wenn ich also Code durchlese und ein Resultoder sehe Wait, ist das eine sofortige Warnflagge. Das Result/ Waitist nur dann korrekt, wenn Sie absolut sicher sind, dass die Aufgabe bereits abgeschlossen ist. Dies ist nicht nur auf einen Blick schwer zu erkennen (im realen Code), sondern es ist auch spröder, Codeänderungen vorzunehmen.

Das ist nicht zu sagen Result/ Waitsollte niemals verwendet werden. Ich folge diesen Richtlinien in meinem eigenen Code:

  1. Asynchroner Code in einer Anwendung kann nur verwendet werden await.
  2. Asynchroner Dienstprogrammcode (in einer Bibliothek) kann gelegentlich Result/ verwenden, Waitwenn der Code dies wirklich erfordert. Eine solche Verwendung sollte wahrscheinlich Kommentare enthalten.
  3. Paralleler Aufgabencode kann Resultund verwenden Wait.

Beachten Sie, dass (1) bei weitem der häufigste Fall ist, daher meine Tendenz, awaitüberall zu verwenden und die anderen Fälle als Ausnahmen von der allgemeinen Regel zu behandeln.

Stephen Cleary
quelle
Wir sind in unseren Projekten auf den Deadlock gestoßen, indem wir "Ergebnis" anstelle von "Warten" verwendet haben. Der durcheinandergebrachte Teil hat keinen Kompilierungsfehler und Ihr Code wird nach einer Weile flockig.
Ahmad Mousavi
@ Stephen würden Sie mir bitte erklären, warum "im Idealfall sollte asynchroner Code überhaupt nicht mit AggregateException umgehen müssen, es sei denn, er möchte dies ausdrücklich"
vcRobe
4
@vcRobe Weil awaitverhindert den AggregateExceptionWrapper. AggregateExceptionwurde für die parallele Programmierung entwickelt, nicht für die asynchrone Programmierung.
Stephen Cleary
2
> "Warten ist nur dann richtig, wenn Sie absolut sicher sind, dass die Aufgabe bereits abgeschlossen ist." .... warum heißt es dann Warten?
Ryan The Leach
4
@RyanTheLeach: Der ursprüngliche Zweck von Waitwar die Verknüpfung mit Instanzen für dynamische Aufgabenparallelität Task . Das Warten auf asynchrone TaskInstanzen ist gefährlich. Microsoft erwog die Einführung eines neuen "Promise" -Typs, entschied sich jedoch dafür, Taskstattdessen den vorhandenen zu verwenden. Der Nachteil der Wiederverwendung des vorhandenen TaskTyps für asynchrone Aufgaben besteht darin, dass Sie am Ende mehrere APIs haben, die im asynchronen Code einfach nicht verwendet werden sollten.
Stephen Cleary
12

Dies ist sinnvoll, wenn timeoutTaskes sich um ein Produkt handelt, von Task.Delaydem ich glaube, dass es in dem Buch steht.

Task.WhenAnyGibt zurück Task<Task>, wobei die innere Aufgabe eine der Aufgaben ist, die Sie als Argumente übergeben haben. Es könnte so umgeschrieben werden:

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

In beiden Fällen gibt es, da downloadTaskbereits abgeschlossen, einen sehr geringen Unterschied zwischen return await downloadTaskund return downloadTask.Result. Es ist so, dass letzterer wirft, AggregateExceptionwas jede ursprüngliche Ausnahme umschließt, wie @KirillShlenskiy in den Kommentaren hervorhob. Ersteres würde nur die ursprüngliche Ausnahme erneut auslösen.

In beiden Fällen sollten Sie, wo immer Sie Ausnahmen behandeln, nach AggregateExceptionund nach den inneren Ausnahmen suchen, um zur Fehlerursache zu gelangen.

noseratio
quelle