Warten auf eine abgeschlossene Aufgabe wie task.Result?

Ich lese gerade “Concurrency in C # Cookbook” von Stephen Cleary, und ich bemerkte die folgende Technik:

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

downloadTask ist ein Aufruf von httpclient.GetStringAsync und timeoutTask führt Task.Delay aus.

Für den Fall, dass es keine Zeitüberschreitung gab, ist downloadTask bereits abgeschlossen. Warum ist eine zweite Wartezeit erforderlich, anstatt downloadTask.Result zurückzugeben, da die Aufgabe bereits abgeschlossen ist?

   

Es gibt schon einige gute Antworten / Kommentare hier, aber nur um zu spielen …

Es gibt zwei Gründe, warum ich lieber über Result (oder Wait ) Wait . Die erste ist, dass die Fehlerbehandlung anders ist; await wird die Ausnahme nicht in eine AggregateException . Im Idealfall sollte sich asynchroner Code niemals mit AggregateException befassen, es sei denn, er möchte dies ausdrücklich tun.

Der zweite Grund ist etwas subtiler. Wie ich in meinem Blog (und im Buch) beschrieben habe, kann Result / Wait Deadlocks verursachen und bei Verwendung in einer async Methode zu noch subtileren Deadlocks führen . Also, wenn ich Code durchlese und ich ein Result sehe oder Wait , ist das eine sofortige Warnflagge. Das Result / Wait ist nur korrekt, wenn Sie absolut sicher sind, dass die Aufgabe bereits abgeschlossen ist. Dies ist nicht nur auf einen Blick (in realem Code) schwer zu sehen, sondern auch spröder für Codeänderungen.

Das soll nicht heißen, dass Result / Wait niemals benutzt werden sollte. Ich folge diesen Richtlinien in meinem eigenen Code:

  1. Asynchroner Code in einer Anwendung kann nur ” await .
  2. Asynchroner Dienstprogrammcode (in einer Bibliothek) kann gelegentlich Result / Wait wenn der Code wirklich danach fragt. Eine solche Verwendung sollte wahrscheinlich Kommentare haben.
  3. Paralleler Aufgabencode kann Result and Wait .

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

Dies ist sinnvoll, wenn timeoutTask ein Produkt von Task.Delay , was ich glaube, was es in dem Buch ist.

Task.WhenAny gibt Task , wobei die innere Task eine der als Argument übergebenen Tasks ist. Es könnte so umgeschrieben werden:

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

In beiden Fällen gibt es, da downloadTask bereits abgeschlossen wurde, einen sehr kleinen Unterschied zwischen der return await downloadTask und der return downloadTask.Result . In diesem Fall wird die AggregateException ausgetriggers, die jede ursprüngliche Ausnahme umschließt, wie von @KirillShlenskiy in den Kommentaren angegeben. Ersteres würde nur die ursprüngliche Ausnahme erneut casting.

In beiden Fällen sollten Sie unabhängig davon, wo Sie mit Ausnahmen arbeiten, nach der AggregateException und ihren inneren Ausnahmen suchen, um die Ursache des Fehlers zu ermitteln.