Eu tenho 3 tarefas:
private async Task<Cat> FeedCat() {}
private async Task<House> SellHouse() {}
private async Task<Tesla> BuyCar() {}
Todos eles precisam ser executados antes que meu código possa continuar e eu também preciso dos resultados de cada um. Nenhum dos resultados tem algo em comum entre si
Como ligo e espero que as 3 tarefas sejam concluídas e depois obtenho os resultados?
c#
.net
async-await
task-parallel-library
.net-4.5
Ian Vink
fonte
fonte
Respostas:
Depois de usar
WhenAll
, você pode obter os resultados individualmente comawait
:Você também pode usá-lo
Task.Result
(já que, nesse ponto, todos eles foram concluídos com êxito). No entanto, eu recomendo o uso,await
porque está claramente correto, enquantoResult
pode causar problemas em outros cenários.fonte
WhenAll
completamente isso; os esperas cuidam para garantir que você não passe das três atribuições posteriores até que as tarefas sejam concluídas.Task.WhenAll()
permite executar a tarefa no modo paralelo . Não consigo entender por que o @Servy sugeriu removê-lo. Sem oWhenAll
que será executado um por umcatTask
já está em execução no momento em que é retornadoFeedCat
. Portanto, qualquerawait
uma dessas abordagens funcionará - a única questão é se você deseja que elas sejam uma de cada vez ou todas juntas. O tratamento de erros é um pouco diferente - se você usarTask.WhenAll
,await
todos eles serão executados, mesmo que um deles falhe mais cedo.WhenAll
não afeta quando as operações são executadas ou como são executadas. Ele só tem possibilidade de afetar como os resultados são observados. Nesse caso em particular, a única diferença é que um erro em um dos dois primeiros métodos resultaria na exceção ser lançada nessa pilha de chamadas mais cedo no meu método do que na de Stephen (embora o mesmo erro sempre seja gerado, se houver algum )Apenas
await
as três tarefas separadamente, depois de iniciar todas elas.fonte
Task.WhenAll
alterações literalmente nada sobre o comportamento do programa, de qualquer maneira observável. É uma chamada de método puramente redundante. Você pode adicioná-lo, se quiser, como uma opção estética, mas isso não muda o que o código faz. O tempo de execução do código será idêntico ou não à chamada de método (bem, tecnicamente haverá uma sobrecarga muito pequena para a chamadaWhenAll
, mas isso deve ser insignificante), apenas tornando a versão um pouco mais demorada do que esta versão.WhenAll
é uma mudança puramente estética. A única diferença observável no comportamento é se você espera que as tarefas posteriores sejam concluídas se uma tarefa anterior falhar, o que normalmente não é necessário. Se você não acredita nas inúmeras explicações sobre por que sua afirmação não é verdadeira, basta executar o código por conta própria e verificar que não é verdade.Se você estiver usando o C # 7, poderá usar um método prático de wrapper como este ...
... para ativar uma sintaxe conveniente como esta quando você desejar aguardar várias tarefas com diferentes tipos de retorno. Você teria que fazer várias sobrecargas para diferentes números de tarefas, é claro.
No entanto, consulte a resposta de Marc Gravell para algumas otimizações em torno do ValueTask e tarefas já concluídas, se você pretende transformar este exemplo em algo real.
fonte
Task.WhenAll()
não está retornando uma tupla. Um está sendo construído a partir dasResult
propriedades das tarefas fornecidas após a conclusão da tarefa retornadaTask.WhenAll()
..Result
chamadas de acordo com o raciocínio de Stephen para evitar que outras pessoas perpetuem a má prática, copiando seu exemplo.Dadas três tarefas -
FeedCat()
,SellHouse()
eBuyCar()
, 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:
Agora, uma abordagem simples seria:
mas ... isso não é conveniente para processar os resultados; nós normalmente queremos
await
isso: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 umaasync
operação e apenasawait
uma por vez: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 paraasync
/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: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:preferem
ValueTask<T>
paraTask<T>
se há uma boa chance das coisas nunca completamente síncrona com muitos valores de retorno diferentes:se possível, preferem
IsCompletedSuccessfully
aStatus == TaskStatus.RanToCompletion
; isso agora existe no .NET Core paraTask
e em qualquer lugar paraValueTask<T>
fonte
Task
quando tudo é feito sem usar os resultados.await
obter a semântica "melhor" das exceções, supondo que as exceções sejam raras, mas significativas.Você pode armazená-los em tarefas e aguardar todos:
fonte
var catTask = FeedCat()
executa a funçãoFeedCat()
e armazena o resultado paracatTask
tornar aawait Task.WhenAll()
parte meio inútil, pois o método já foi executado?Caso esteja tentando registrar todos os erros, mantenha a linha Task.WhenAll em seu código, muitos comentários sugerem que você pode removê-lo e aguardar tarefas individuais. Task.WhenAll é realmente importante para o tratamento de erros. Sem essa linha, você potencialmente deixa seu código aberto para exceções não observadas.
Imagine o FeedCat lança uma exceção no seguinte código:
Nesse caso, você nunca esperará na houseTask nem na carTask. Existem três cenários possíveis aqui:
O SellHouse já foi concluído com êxito quando o FeedCat falhou. Neste caso, você está bem.
O SellHouse não está completo e falha com exceção em algum momento. A exceção não é observada e será repetida no encadeamento do finalizador.
SellHouse não está completo e contém aguarda dentro dele. Caso seu código seja executado no ASP.NET SellHouse falhará assim que algumas das esperas forem concluídas dentro dele. Isso acontece porque você basicamente acionou o esquecimento de chamada e sincronização e perdeu e o contexto de sincronização foi perdido assim que o FeedCat falhou.
Aqui está o erro que você obterá no caso (3):
No caso (2), você receberá um erro semelhante, mas com o rastreamento da pilha de exceção original.
Para o .NET 4.0 e posterior, você pode capturar exceções não observadas usando TaskScheduler.UnobservedTaskException. Para o .NET 4.5 e posteriores, as exceções não observadas são engolidas por padrão. A exceção não observada do .NET 4.0 travará seu processo.
Mais detalhes aqui: Tratamento de exceções de tarefas no .NET 4.5
fonte
Você pode usar
Task.WhenAll
como mencionado ouTask.WaitAll
, dependendo se deseja que o encadeamento aguarde. Dê uma olhada no link para obter uma explicação de ambos.WaitAll vs WhenAll
fonte
Use
Task.WhenAll
e aguarde os resultados:fonte
Aviso de encaminhamento
Apenas um aviso rápido para quem visita esse e outros threads semelhantes, procurando uma maneira de paralelizar o EntityFramework usando o conjunto de ferramentas assíncrona + aguardar + tarefa : o padrão mostrado aqui é bom, no entanto, quando se trata do floco de neve especial da EF, você não irá obtenha execução paralela, a menos e até que você use uma instância de contexto db (nova) separada dentro de cada chamada * Async () envolvida.
Esse tipo de coisa é necessária devido às limitações inerentes ao design dos contextos ef-db, que proíbem a execução de várias consultas em paralelo na mesma instância do contexto ef-db.
Aproveitando as respostas já fornecidas, esta é a maneira de garantir que você colete todos os valores, mesmo no caso de uma ou mais tarefas resultar em uma exceção:
Uma implementação alternativa que possui mais ou menos as mesmas características de desempenho pode ser:
fonte
se você deseja acessar o Cat, faça o seguinte:
Isso é muito simples de fazer e muito útil de usar, não há necessidade de buscar uma solução complexa.
fonte
dynamic
é o diabo. É para interoperabilidade COM complicada e tal, e não deve ser usado em qualquer situação em que não seja absolutamente necessário. Especialmente se você se importa com o desempenho. Ou digite segurança. Ou refatoração. Ou depuração.