Neste código:
private async void button1_Click(object sender, EventArgs e) {
try {
await Task.WhenAll(DoLongThingAsyncEx1(), DoLongThingAsyncEx2());
}
catch (Exception ex) {
// Expect AggregateException, but got InvalidTimeZoneException
}
}
Task DoLongThingAsyncEx1() {
return Task.Run(() => { throw new InvalidTimeZoneException(); });
}
Task DoLongThingAsyncEx2() {
return Task.Run(() => { throw new InvalidOperation();});
}
Eu esperava WhenAll
criar e lançar um AggregateException
, já que pelo menos uma das tarefas que ele estava esperando gerou uma exceção. Em vez disso, estou recebendo de volta uma única exceção lançada por uma das tarefas.
Será que WhenAll
nem sempre criar um AggregateException
?
.net
exception
asynchronous
tap
Michael Ray Lovett
fonte
fonte
AggregateException
. Se você usou emTask.Wait
vez deawait
em seu exemplo, você pegariaAggregateException
Task.WhenAll
e caí na mesma armadilha. Tentei entrar em detalhes sobre esse comportamento.Respostas:
Não me lembro exatamente onde, mas li em algum lugar que, com novas palavras-chave async / await , elas se desdobram
AggregateException
na exceção real.Portanto, no bloco catch, você obtém a exceção real e não a agregada. Isso nos ajuda a escrever um código mais natural e intuitivo.
Isso também era necessário para facilitar a conversão do código existente em async / await, onde muitos códigos esperam exceções específicas e não exceções agregadas.
- Editar -
Entendi:
An Async Primer de Bill Wagner
fonte
Sei que essa é uma pergunta que já foi respondida, mas a resposta escolhida não resolve realmente o problema do OP, então pensei em postar isso.
Esta solução fornece a exceção agregada (ou seja, todas as exceções que foram lançadas pelas várias tarefas) e não bloqueia (o fluxo de trabalho ainda é assíncrono).
A chave é salvar uma referência à tarefa agregada antes de esperá-la, então você pode acessar sua propriedade Exception que contém sua AggregateException (mesmo se apenas uma tarefa gerou uma exceção).
Espero que ainda seja útil. Eu sei que tive esse problema hoje.
fonte
throw task.Exception;
ocatch
bloco dentro ? (Task.IsCanceled
) não é propagado corretamente. Isso pode ser resolvido usando um auxiliar de extensão como este .Você pode percorrer todas as tarefas para ver se mais de uma gerou uma exceção:
fonte
WhenAll
sai na primeira exceção e retorna isso. consulte: stackoverflow.com/questions/6123406/waitall-vs-whenallexceptions
contém ambas as exceções lançadas.await
faz com que a primeira exceção seja desembrulhada, mas todas as exceções ainda estão disponíveis por meio do array de Tarefas.Apenas pensei em expandir a resposta de @ Richiban para dizer que você também pode manipular AggregateException no bloco catch referenciando-o na tarefa. Por exemplo:
fonte
Você está pensando
Task.WaitAll
- lança umAggregateException
.WhenAll apenas lança a primeira exceção da lista de exceções que encontra.
fonte
WhenAll
método tem umaException
propriedade queAggregateException
contém todas as exceções lançadas em seuInnerExceptions
. O que está acontecendo aqui éawait
lançar a primeira exceção interna em vez daAggregateException
própria (como o deciclone disse). Chamar oWait
método da tarefa em vez de esperá-lo faz com que a exceção original seja lançada.Muitas respostas boas aqui, mas eu ainda gostaria de postar meu discurso retórico, pois acabei de encontrar o mesmo problema e conduzi algumas pesquisas. Ou pule para a versão TLDR abaixo.
O problema
Aguardar o
task
retornado porTask.WhenAll
apenas lança a primeira exceção doAggregateException
armazenado emtask.Exception
, mesmo quando várias tarefas falharam.Os documentos atuais para
Task.WhenAll
dizer:O que está correto, mas não diz nada sobre o comportamento de "desembrulhar" mencionado acima de quando a tarefa retornada é aguardada.
Suponho que os documentos não mencionem isso porque esse comportamento não é específico para
Task.WhenAll
.É simplesmente que
Task.Exception
é do tipoAggregateException
e paraawait
continuações sempre é desembrulhado como sua primeira exceção interna, por design. Isso é ótimo para a maioria dos casos, porque geralmenteTask.Exception
consiste em apenas uma exceção interna. Mas considere este código:Aqui, uma instância de
AggregateException
é desempacotada em sua primeira exceção internaInvalidOperationException
exatamente da mesma maneira que poderíamos ter feitoTask.WhenAll
. Poderíamos ter falhado em observarDivideByZeroException
se não tivéssemos passadotask.Exception.InnerExceptions
diretamente.Stephen Toub, da Microsoft, explica a razão por trás desse comportamento no problema relacionado do GitHub :
Outra coisa importante a se notar, esse comportamento de desembrulhar é superficial. Ou seja, ele apenas desembrulhará a primeira exceção
AggregateException.InnerExceptions
e a deixará lá, mesmo se acontecer de ser uma instância de outraAggregateException
. Isso pode adicionar mais uma camada de confusão. Por exemplo, vamos mudarWhenAllWrong
assim:Uma solução (TLDR)
Então, voltando ao
await Task.WhenAll(...)
, o que eu pessoalmente queria é ser capaz de:AggregateException
se mais de uma exceção tiver sido lançada coletivamente por uma ou mais tarefas;Task
apenas para verificar seuTask.Exception
;Task.IsCanceled
), como algo como isso não faria isso:Task t = Task.WhenAll(...); try { await t; } catch { throw t.Exception; }
.Eu juntei a seguinte extensão para isso:
Agora, o seguinte funciona da maneira que eu quero:
fonte
Isso funciona para mim
fonte
WhenAll
não é o mesmo queWhenAny
.await Task.WhenAny(tasks)
será concluído assim que qualquer tarefa for concluída. Portanto, se você tiver uma tarefa que é concluída imediatamente e é bem-sucedida e outra leva alguns segundos antes de lançar uma exceção, ela retornará imediatamente sem nenhum erro.Em seu código, a primeira exceção é retornada por design, conforme explicado em http://blogs.msdn.com/b/pfxteam/archive/2011/09/28/task-exception-handling-in-net-4-5. aspx
Quanto à sua pergunta, você receberá a AggreateException se escrever um código como este:
fonte