Angesichts von drei Aufgaben - FeedCat()
, SellHouse()
und BuyCar()
gibt es zwei interessante Fälle: entweder sie alle werden synchron abgeschlossen (aus irgendeinem Grund, vielleicht Caching oder ein Fehler), oder sie werden es 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; typischerweise möchten wir das await
:
async Task DoTheThings() {
Task x = FeedCat();
Task y = SellHouse();
Task z = BuyCar();
await Task.WhenAll(x, y, z);
// vermutlich möchten wir etwas mit den Ergebnissen tun...
return DoWhatever(x.Result, y.Result, z.Result);
}
aber das erzeugt viel Overhead und allokiert verschiedene Arrays (einschließlich des params Task[]
arrays) und Listen (intern). Es funktioniert, ist aber meiner Meinung nach nicht ideal. In vielerlei Hinsicht ist es einfacher, eine async
-Operation zu verwenden und einfach nacheinander darauf zu warten:
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);
}
Im Gegensatz zu einigen der obigen Kommentare hat es keinen Unterschied, ob man await
anstelle von Task.WhenAll
verwendet, wie die Aufgaben ausgeführt werden (gleichzeitig, nacheinander usw.). Auf höchster Ebene vorwegnimmt Task.WhenAll
eine gute Compiler-Unterstützung für async
/await
und war nützlich als diese Dinge noch nicht existierten. Es ist auch nützlich, wenn man ein willkürliches Array von Aufgaben hat, anstatt 3 diskrete Aufgaben.
Aber: wir haben immer noch das Problem, dass async
/await
viel Compilerlärm 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 Ausweichpfad einbauen:
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,
// zum Abschluss gebracht zu werden
return Awaited(x, y, z);
}
async Task Awaited(Task a, Task b, Task c) {
return DoWhatever(await x, await y, await z);
}
Dieser Ansatz "synchroner Pfad mit asynchronem Ausweichpfad" wird immer häufiger, insbesondere in hochleistungsfähigen Codes, wo synchrone Abschlüsse relativ häufig sind. Beachten Sie, dass es überhaupt nicht hilft, wenn der Abschluss immer wirklich asynchron ist.
Zusätzliche Dinge, die hier gelten:
-
bei aktuellem C# ist ein häufiges Muster, dass die async
-Ausweichmethode häufig als lokale Funktion implementiert wird:
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,
// zum Abschluss gebracht zu werden
return Awaited(x, y, z);
}
-
bevorzugen Sie ValueTask
gegenüber Task
, wenn es eine gute Chance gibt, dass Dinge jemals vollständig synchron enden mit vielen verschiedenen Rückgabewerten:
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,
// zum Abschluss gebracht zu werden
return Awaited(x, y, z);
}
-
wenn möglich, bevorzugen Sie IsCompletedSuccessfully
gegenüber Status == TaskStatus.RanToCompletion
; das existiert jetzt für Task
in .NET Core und überall für ValueTask