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 asyncOperation zu verwenden, und zwar awaitjeweils 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 awaitanstelle 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.WhenAllTask.WhenAll asyncawait
Aber: Wir haben immer noch das Problem, dass async/ awaitviel 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 asyncFallback-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 IsCompletedSuccessfullyzu Status == TaskStatus.RanToCompletion; Dies gibt es jetzt in .NET Core für Taskund überall fürValueTask<T>