Dadas três tarefas - FeedCat()
, SellHouse()
e BuyCar()
, existem dois casos interessantes: todos eles são concluídos de forma síncrona (por algum motivo, talvez cache ou erro), ou não.
Digamos que temos, a partir da pergunta:
Task<string> DoTheThings() {
Task<Cat> x = FeedCat();
Task<House> y = SellHouse();
Task<Tesla> z = BuyCar();
// what here?
}
Agora, uma abordagem simples seria:
Task.WhenAll(x, y, z);
mas ... isso não é conveniente para processar os resultados; nós normalmente queremos await
isso:
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);
}
mas isso gera muita sobrecarga e aloca várias matrizes (incluindo a params Task[]
matriz) e listas (internamente). Funciona, mas não é ótimo IMO. De muitas maneiras, é mais simples usar uma async
operação e apenas await
uma por vez:
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);
}
Ao contrário de alguns dos comentários acima, usar em await
vez de nãoTask.WhenAll
faz diferença na maneira como as tarefas são executadas (simultaneamente, sequencialmente, etc.). No nível mais alto, Task.WhenAll
antecede o bom suporte do compilador para async
/ await
e foi útil quando essas coisas não existiam . Também é útil quando você tem uma variedade arbitrária de tarefas, em vez de três tarefas discretas.
Mas: ainda temos o problema que async
/ await
gera muito ruído do compilador para a continuação. Se é provável que as tarefas pode realmente completar de forma síncrona, então podemos otimizar isso através da construção de um caminho síncrona com um fallback assíncrona:
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);
}
Essa abordagem "caminho de sincronização com fallback assíncrono" é cada vez mais comum, especialmente em código de alto desempenho, onde conclusões síncronas são relativamente frequentes. Observe que não ajudará em nada se a conclusão for sempre genuinamente assíncrona.
Coisas adicionais que se aplicam aqui:
com o C # recente, um padrão comum é o async
método de fallback geralmente implementado como uma função local:
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);
}
preferem ValueTask<T>
para Task<T>
se há uma boa chance das coisas nunca completamente síncrona com muitos valores de retorno diferentes:
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 possível, preferem IsCompletedSuccessfully
a Status == TaskStatus.RanToCompletion
; isso agora existe no .NET Core para Task
e em qualquer lugar paraValueTask<T>