Gegeben drei Aufgaben - FeedCat()
, SellHouse()
und BuyCar()
gibt es zwei interessante Fälle: Entweder sie alle vollständig synchron (aus irgendeinem Grund, vielleicht das Caching oder ein Fehler), oder sie es nicht tun.
Nehmen wir an, wir haben aus der Frage:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
Ein einfacher Ansatz wäre nun:
Task.WhenAll(x, y, z);
aber ... das ist nicht bequem für die Verarbeitung der Ergebnisse; Das möchten wir normalerweise await
:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
await Task.WhenAll(x, y, z);
// presumably we want to do something with the results...
return DoWhatever(x.Result, y.Result, z.Result);
}
Dies verursacht jedoch viel Overhead und weist verschiedene Arrays (einschließlich des params Task[]
Arrays) und Listen (intern) zu. Es funktioniert, aber es ist nicht großartig, IMO. In vielerlei Hinsicht ist es einfacher , eine async
Operation zu verwenden, und zwar await
jeweils nacheinander:
async Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// do something with the results...
return DoWhatever(await x, await y, await z);
}
Im Gegensatz zu einigen der obigen Kommentare macht die Verwendung von await
anstelle von keinen Unterschied für die Ausführung der Aufgaben (gleichzeitig, nacheinander usw.). Auf höchster Ebene vor einer guten Compiler-Unterstützung für / und war nützlich, wenn diese Dinge nicht existierten . Dies ist auch nützlich, wenn Sie eine beliebige Reihe von Aufgaben anstelle von drei diskreten Aufgaben haben.Task.WhenAll
Task.WhenAll
async
await
Aber: Wir haben immer noch das Problem, dass async
/ await
viel Compiler-Rauschen für die Fortsetzung erzeugt. Ist es wahrscheinlich , dass die Aufgaben ist vielleicht tatsächlich synchron abgeschlossen hat , dann können wir diese optimieren , indem sie mit einem asynchronen Rückfall in einem synchronen Pfad Aufbau:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> 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));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
async Task Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await x, await y, await z);
}
Dieser Ansatz "Synchronisierungspfad mit asynchronem Fallback" wird immer häufiger verwendet, insbesondere bei Hochleistungscode, bei dem synchrone Abschlüsse relativ häufig sind. Beachten Sie, dass es überhaupt nicht hilft, wenn die Fertigstellung immer wirklich asynchron ist.
Zusätzliche Dinge, die hier gelten:
Mit dem aktuellen C # wird ein allgemeines Muster für die async
Fallback-Methode verwendet, die üblicherweise als lokale Funktion implementiert wird:
Task<string> DoTheThings() {
async Task<string> Awaited(Task<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> 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));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
lieber ValueTask<T>
, Task<T>
wenn es eine gute Chance gibt, dass die Dinge jemals vollständig synchron mit vielen verschiedenen Rückgabewerten sind:
ValueTask<string> DoTheThings() {
async ValueTask<string> Awaited(ValueTask<Cat> a, Task<House> b, Task<Tesla> c) {
return DoWhatever(await a, await b, await c);
}
ValueTask<Cat> x = FeedCat();
ValueTask<House> y = SellHouse();
ValueTask<Tesla> z = BuyCar();
if(x.IsCompletedSuccessfully &&
y.IsCompletedSuccessfully &&
z.IsCompletedSuccessfully)
return new ValueTask<string>(
DoWhatever(a.Result, b.Result, c.Result));
// we can safely access .Result, as they are known
// to be ran-to-completion
return Awaited(x, y, z);
}
wenn möglich, lieber IsCompletedSuccessfully
zu Status == TaskStatus.RanToCompletion
; Dies gibt es jetzt in .NET Core für Task
und überall fürValueTask<T>