Sollte mir die Reihenfolge der Aufgabenerfüllung egal sein und ich brauche nur alle, um sie zu erledigen, sollte ich sie immer noch await Task.WhenAll
anstelle von mehreren verwenden await
? zB ist DoWork2
unten eine bevorzugte Methode zu DoWork1
(und warum?):
using System;
using System.Threading.Tasks;
namespace ConsoleApp
{
class Program
{
static async Task<string> DoTaskAsync(string name, int timeout)
{
var start = DateTime.Now;
Console.WriteLine("Enter {0}, {1}", name, timeout);
await Task.Delay(timeout);
Console.WriteLine("Exit {0}, {1}", name, (DateTime.Now - start).TotalMilliseconds);
return name;
}
static async Task DoWork1()
{
var t1 = DoTaskAsync("t1.1", 3000);
var t2 = DoTaskAsync("t1.2", 2000);
var t3 = DoTaskAsync("t1.3", 1000);
await t1; await t2; await t3;
Console.WriteLine("DoWork1 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static async Task DoWork2()
{
var t1 = DoTaskAsync("t2.1", 3000);
var t2 = DoTaskAsync("t2.2", 2000);
var t3 = DoTaskAsync("t2.3", 1000);
await Task.WhenAll(t1, t2, t3);
Console.WriteLine("DoWork2 results: {0}", String.Join(", ", t1.Result, t2.Result, t3.Result));
}
static void Main(string[] args)
{
Task.WhenAll(DoWork1(), DoWork2()).Wait();
}
}
}
await t1; await t2; ....; await tn
=> der zweite ist in beiden Fällen immer die beste WahlAntworten:
Ja, verwenden
WhenAll
Sie , weil alle Fehler gleichzeitig weitergegeben werden. Mit den mehreren Wartezeiten verlieren Sie Fehler, wenn einer der früheren Würfe wartet.Ein weiterer wichtiger Unterschied besteht darin, dass darauf
WhenAll
gewartet wird, dass alle Aufgaben auch bei Fehlern (fehlerhafte oder abgebrochene Aufgaben) abgeschlossen sind. Manuelles Warten nacheinander würde zu unerwarteter Parallelität führen, da der Teil Ihres Programms, der warten möchte, tatsächlich vorzeitig fortgesetzt wird.Ich denke, es erleichtert auch das Lesen des Codes, da die gewünschte Semantik direkt im Code dokumentiert ist.
quelle
await
das Ergebnis.Mein Verständnis ist, dass der Hauptgrund
Task.WhenAll
, mehrereawait
s vorzuziehen , die Leistung / Aufgabe "churning" ist: DieDoWork1
Methode macht ungefähr so:Im Gegensatz
DoWork2
dazu:Ob dies für Ihren speziellen Fall groß genug ist, ist natürlich "kontextabhängig" (entschuldigen Sie das Wortspiel).
quelle
Eine asynchrone Methode ist als Zustandsmaschine implementiert. Es ist möglich, Methoden so zu schreiben, dass sie nicht in Zustandsmaschinen kompiliert werden. Dies wird häufig als schnelle asynchrone Methode bezeichnet. Diese können folgendermaßen implementiert werden:
Bei Verwendung ist
Task.WhenAll
es möglich, diesen Fast-Track-Code beizubehalten und gleichzeitig sicherzustellen, dass der Anrufer warten kann, bis alle Aufgaben erledigt sind, z.quelle
(Haftungsausschluss: Diese Antwort stammt aus Ian Griffiths TPL Async-Kurs zu Pluralsight )
Ein weiterer Grund, WhenAll zu bevorzugen, ist die Ausnahmebehandlung.
Angenommen, Sie hatten einen Try-Catch-Block für Ihre DoWork-Methoden und nehmen an, dass sie verschiedene DoTask-Methoden aufrufen:
In diesem Fall wird nur die erste abgefangen, wenn alle drei Aufgaben Ausnahmen auslösen. Jede spätere Ausnahme geht verloren. Dh wenn t2 und t3 eine Ausnahme auslösen, wird nur t2 abgefangen; usw. Die Ausnahmen für nachfolgende Aufgaben bleiben unbemerkt.
Wo wie in WhenAll - Wenn eine oder alle Aufgaben fehlerhaft sind, enthält die resultierende Aufgabe alle Ausnahmen. Das Schlüsselwort await löst immer noch die erste Ausnahme aus. Die anderen Ausnahmen bleiben also praktisch unbemerkt. Eine Möglichkeit, dies zu überwinden, besteht darin, nach der Aufgabe WhenAll eine leere Fortsetzung hinzuzufügen und das Warten dort abzulegen. Auf diese Weise löst die Ergebniseigenschaft die vollständige Aggregatausnahme aus, wenn die Aufgabe fehlschlägt:
quelle
Die anderen Antworten auf diese Frage bieten technische Gründe, warum
await Task.WhenAll(t1, t2, t3);
bevorzugt wird. Diese Antwort zielt darauf ab, sie von einer weicheren Seite zu betrachten (auf die @usr anspielt), während sie immer noch zu dem gleichen Schluss kommt.await Task.WhenAll(t1, t2, t3);
ist ein funktionalerer Ansatz, da er die Absicht erklärt und atomar ist.Mit
await t1; await t2; await t3;
, es verhindert , dass nichts einen Mitspieler (oder vielleicht sogar Ihre Zukunft selbst!) Vom Hinzufügen von Code zwischen dem einzelnenawait
Aussagen. Sicher, Sie haben es auf eine Zeile komprimiert, um dies im Wesentlichen zu erreichen, aber das löst das Problem nicht. Außerdem ist es in einer Teameinstellung im Allgemeinen eine schlechte Form, mehrere Anweisungen in eine bestimmte Codezeile aufzunehmen, da dies das Scannen der Quelldatei für das menschliche Auge erschweren kann.Einfach ausgedrückt,
await Task.WhenAll(t1, t2, t3);
ist wartbarer, da es Ihre Absicht klarer kommuniziert und weniger anfällig für besondere Fehler ist, die durch wohlmeinende Aktualisierungen des Codes oder sogar durch fehlerhafte Zusammenführungen entstehen können.quelle