Dati tre compiti FeedCat(), SellHouse()e BuyCar()ci sono due casi interessanti: o si completano tutti in modo sincrono (per qualche motivo, forse nella cache o in un errore), oppure no.
Diciamo che abbiamo, dalla domanda:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
Ora, un approccio semplice sarebbe:
Task.WhenAll(x, y, z);
ma ... non è conveniente per elaborare i risultati; in genere vorremmo awaitche:
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);
}
ma ciò comporta un sacco di sovraccarico e alloca vari array (incluso l' params Task[]array) ed elenchi (internamente). Funziona, ma non è eccezionale IMO. In molti modi è più semplice usare asyncun'operazione e solo awaita turno:
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);
}
Contrariamente ad alcuni dei commenti sopra, l'utilizzo awaitinvece di nonTask.WhenAll fa alcuna differenza sul modo in cui le attività vengono eseguite (contemporaneamente, in sequenza, ecc.). Al livello più alto, Task.WhenAll precede un buon supporto del compilatore per async/ awaited era utile quando quelle cose non esistevano . È anche utile quando si dispone di una serie arbitraria di attività, anziché di 3 attività discrete.
Ma: abbiamo ancora il problema che async/ awaitgenera molto rumore del compilatore per la continuazione. Se è probabile che le attività possano effettivamente essere completate in modo sincrono, allora possiamo ottimizzarlo costruendo in un percorso sincrono con un fallback asincrono:
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);
}
Questo approccio "sync path with async fallback" è sempre più comune soprattutto nel codice ad alte prestazioni in cui i completamenti sincroni sono relativamente frequenti. Nota che non sarà affatto utile se il completamento è sempre sinceramente asincrono.
Altre cose che si applicano qui:
con il recente C #, un modello comune è che il asyncmetodo di fallback è comunemente implementato come funzione locale:
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);
}
preferire ValueTask<T>a Task<T>se v'è una buona probabilità di cose mai completamente sincrono con molti valori di ritorno differenti:
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);
}
se possibile, preferire IsCompletedSuccessfullya Status == TaskStatus.RanToCompletion; questo ora esiste in .NET Core per Taske ovunque perValueTask<T>