Angesichts drei Aufgaben - FeedCat()
, SellHouse()
und BuyCar()
gibt es zwei interessante Fälle: Entweder sie alle werden synchron abgeschlossen (aus irgendeinem Grund, vielleicht durch Caching oder einen Fehler), oder nicht.
Sagen wir, wir haben, ausgehend von der Frage:
Task DoTheThings() {
Task x = FeedCat();
Task y = SellHouse();
Task z = BuyCar();
// was hier?
}
Jetzt wäre ein einfacher Ansatz:
Task.WhenAll(x, y, z);
aber ... das ist nicht praktisch für die Verarbeitung der Ergebnisse; normalerweise möchten wir das await
:
async Task DoTheThings() {
Task x = FeedCat();
Task y = SellHouse();
Task z = BuyCar();
await Task.WhenAll(x, y, z);
// wahrscheinlich möchten wir etwas mit den Ergebnissen machen...
return DoWhatever(x.Result, y.Result, z.Result);
}
aber das erzeugt viel Overhead und alloziert verschiedene Arrays (einschließlich des params Task[]
Arrays) und Listen (intern). Es funktioniert, aber meiner Meinung nach ist es nicht optimal. In vielerlei Hinsicht ist es einfacher, eine async
-Operation zu verwenden und einfach nacheinander auf jedes zu await
:
async Task DoTheThings() {
Task x = FeedCat();
Task y = SellHouse();
Task z = BuyCar();
// etwas mit den Ergebnissen machen...
return DoWhatever(await x, await y, await z);
}
Entgegen einiger der Kommentare oben hat es für die Ausführung der Aufgaben (parallel, sequentiell, etc.) keinen Unterschied, ob man await
statt Task.WhenAll
verwendet. Auf höchster Ebene vorvausgeht Task.WhenAll
eine gute Compiler-Unterstützung für async
/await
und war nützlich, als diese Dinge nicht existierten. Es ist auch nützlich, wenn man ein beliebiges Array von Aufgaben hat, anstatt 3 diskrete Aufgaben.
Aber: Wir haben immer noch das Problem, dass async
/await
viel Compiler-Rauschen für die Fortsetzung erzeugt. Wenn es wahrscheinlich ist, dass die Aufgaben tatsächlich synchron abgeschlossen werden könnten, können wir dies optimieren, indem wir einen synchronen Pfad mit einem asynchronen Ausweichweg integrieren:
Task DoTheThings() {
Task x = FeedCat();
Task y = SellHouse();
Task z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// wir können sicher auf .Result zugreifen, da sie bekannt sind,
// um vollständig ausgeführt zu sein
return Awaited(x, y, z);
}
async Task Awaited(Task a, Task b, Task c) {
return DoWhatever(await x, await y, await z);
}
Dieser "Synchroner Pfad mit asynchronem Ausweichweg"-Ansatz ist zunehmend verbreitet, insbesondere in High-Performance-Code, wo synchrone Abschlüsse relativ häufig sind. Beachten Sie, dass dies überhaupt nicht hilft, wenn die Fertigstellung immer wirklich asynchron ist.
Weitere Dinge, die hier gelten:
-
bei aktuellem C# ist ein verbreitetes Muster für die async
-Ausweichmethode häufig als lokale Funktion implementiert:
Task DoTheThings() {
async Task Awaited(Task a, Task b, Task c) {
return DoWhatever(await a, await b, await c);
}
Task x = FeedCat();
Task y = SellHouse();
Task z = BuyCar();
if(x.Status == TaskStatus.RanToCompletion &&
y.Status == TaskStatus.RanToCompletion &&
z.Status == TaskStatus.RanToCompletion)
return Task.FromResult(
DoWhatever(a.Result, b.Result, c.Result));
// wir können sicher auf .Result zugreifen, da sie bekannt sind,
// um vollständig ausgeführt zu sein
return Awaited(x, y, z);
}
-
bevorzugen Sie ValueTask
gegenüber Task
, wenn die Wahrscheinlichkeit besteht, dass Dinge jemals vollständig synchron mit vielen unterschiedlichen Rückgabewerten abgeschlossen werden:
ValueTask DoTheThings() {
async ValueTask Awaited(ValueTask a, Task b, Task c) {
return DoWhatever(await a, await b, await c);
}
ValueTask x = FeedCat();
ValueTask y = SellHouse();
ValueTask z = BuyCar();
if(x.IsCompletedSuccessfully &&
y.IsCompletedSuccessfully &&
z.IsCompletedSuccessfully)
return new ValueTask(
DoWhatever(a.Result, b.Result, c.Result));
// wir können sicher auf .Result zugreifen, da sie bekannt sind,
// um vollständig ausgeführt zu sein
return Awaited(x, y, z);
}
-
wenn möglich, bevorzugen Sie IsCompletedSuccessfully
gegenüber Status == TaskStatus.RanToCompletion
; das existiert jetzt in .NET Core für Task
und überall für ValueTask