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 await
che:
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 async
un'operazione e solo await
a 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 await
invece 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
/ await
ed 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
/ await
genera 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 async
metodo 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 IsCompletedSuccessfully
a Status == TaskStatus.RanToCompletion
; questo ora esiste in .NET Core per Task
e ovunque perValueTask<T>