Minha biblioteca de tarefas assíncronas deve engolir exceções silenciosamente?

10

Acabei de aprender que o .NET 4.5 introduziu uma alteração em como as exceções dentro de um Tasksão tratadas. Ou seja, eles são silenciosamente suprimidos.

O raciocínio oficial sobre por que isso foi feito parece ser "queríamos ser mais amigáveis ​​com desenvolvedores inexperientes":

No .NET 4.5, as Tarefas têm muito mais destaque do que no .NET 4, pois são incorporadas às linguagens C # e Visual Basic como parte dos novos recursos assíncronos suportados pelas linguagens. Isso, com efeito, move as Tarefas para fora do domínio de desenvolvedores experientes, para o reino de todos. Como resultado, também leva a um novo conjunto de vantagens e desvantagens sobre o quão rigoroso é o tratamento de exceções.

( fonte )

Aprendi a confiar que muitas das decisões no .NET foram tomadas por pessoas que realmente sabem o que estão fazendo, e geralmente há uma boa razão por trás das coisas que eles decidem. Mas este me escapa.

Se eu estivesse projetando minha própria biblioteca de tarefas assíncronas, qual é a vantagem de engolir exceções que os desenvolvedores do Framework viram que não estou vendo?

Roman Starkov
fonte
4
+1. Boa pergunta, considerando que não propagar exceções que você não pode manipular é uma má prática, mesmo fora do contexto assíncrono. Além disso, o argumento sobre desenvolvedores inexperientes é bastante manco, IMHO. O que seria mais estranho para iniciantes do que um código que não apenas não funciona, mas também não gera exceções?
Arseni Mourzenko
@MainMa Exatamente meus pensamentos, mas eu já estive errado antes (quando se descobriu que eles tinham, de fato, uma razão muito boa, mas não óbvia), então pensei em perguntar.
Roman Starkov
2
Uau, estou feliz que você postou isso apenas porque é a primeira vez que ouvi falar; e definitivamente viola a POLA . Você está certo que esses caras realmente sabem o que estão fazendo, por isso estou sinceramente curioso sobre o que pode estar por trás disso ... Gostaria de saber se há uma razão mais técnica baseada na implementação do Async e como as exceções se propagariam como inferior sendo passado para uma rotina co ..
Jimmy Hoffa

Respostas:

2

Pelo que vale a pena, o documento ao qual você vinculou fornece um exemplo de caso como justificativa:

Task op1 = FooAsync(); 
Task op2 = BarAsync(); 
await op1; 
await op2;

Nesse código, o desenvolvedor está lançando duas operações assíncronas para serem executadas em paralelo e, em seguida, aguarda de forma assíncrona cada uma usando o novo recurso de linguagem de espera ... [C] considerando o que acontecerá se ambas as opções, op1 e op2. Aguardar op1 propagará a exceção do op1 e, portanto, o op2 nunca será aguardado. Como resultado, a exceção do op2 não será observada e o processo acabará por falhar.

Para tornar mais fácil para os desenvolvedores escrever código assíncrono com base em tarefas, o .NET 4.5 altera o comportamento da exceção padrão para exceções não observadas. Embora as exceções não observadas ainda façam com que o evento UnobservedTaskException seja gerado (se isso não for uma alteração de última hora), o processo não será interrompido por padrão. Em vez disso, a exceção acabará sendo comida após o evento ser gerado, independentemente de um manipulador de eventos observar a exceção.

Não estou convencido disso. Ele remove a possibilidade de um erro inequívoco, mas difícil de rastrear (falha misteriosa do programa que pode ocorrer muito depois do erro real), mas o substitui pela possibilidade de um erro completamente silencioso - que pode se tornar um problema igualmente difícil de rastrear posteriormente no seu programa. Isso parece uma escolha duvidosa para mim.

O comportamento é configurável - mas, é claro, 99% dos desenvolvedores vão apenas usar o comportamento padrão, nunca pensando sobre esse problema. Portanto, o que eles selecionaram como padrão é muito importante.


fonte
Mas você fazer obter uma exceção normal para op1, certo? Se aquele não for tratado, o processo será interrompido, certo?
Timwi
11
@ Timwi, pelo que entendi, nem op1a exceção nem op2a exceção derrubariam o programa. A vantagem é que você tem a oportunidade de observar os dois. Mas se não o fizer, os dois serão engolidos. Eu poderia estar errado, no entanto.
Estou realmente surpreso que eles achem tão importante deixar você "observar" os dois. Se você quiser "observar"-los, você pegá-los e relatar sua ocorrência via o resultado tarefa ... Concordo com o seu raciocínio, +1!
Roman Starkov
2

TL; DR - Não , você não deve simplesmente ignorar as exceções silenciosamente.


Vamos olhar para as suposições.

  • Sua fé cega

Aprendi a confiar que muitas das decisões no .NET foram tomadas por pessoas que realmente sabem o que estão fazendo, e geralmente há uma boa razão por trás das coisas que eles decidem. Mas este me escapa.

MS tem algumas pessoas realmente brilhantes na equipe, sem dúvida. Eles também têm um número de gerentes e executivos ... sem noção, e que sempre tomam más decisões. Por mais que gostássemos de acreditar no conto de fadas do cientista tecnicamente experiente que conduz o desenvolvimento do produto, a realidade é muito mais sombria.

Meu argumento é que, se o seu instinto lhe diz que algo pode estar errado, há uma boa chance (e precedentes anteriores) de estar errado.

  • Que esta é uma questão técnica

Como lidar com exceções nesse caso é uma decisão de fatores humanos, não uma decisão técnica. Vagamente, uma exceção é um erro. A apresentação do erro, mesmo se mal formatada, fornece informações críticas para o usuário final. Ou seja, algo deu errado.

Considere esta conversa hipotética entre o usuário final e um desenvolvedor.

Usuário: o aplicativo está quebrado.
Dev: O que está quebrado?
Usuário: Não sei, clico no botão e nada acontece.
Dev: O que você quer dizer com nada acontece?
Usuário: Olha, eu clico, espero, espero e espero, e nada ... está obviamente quebrado.

Nosso pobre, felizmente hipotético desenvolvedor, agora precisa descobrir o que deu errado na cadeia de eventos. Onde estava?
Notificação do manipulador de eventos -> Rotina para manipular o evento -> Método acionado pelo manipulador -> início da chamada assíncrona -> as 7 camadas da rede OSI -> transmissão física -> faça backup das 7 camadas da rede OSI -> serviço de recebimento -> método chamado pelo serviço -> ... -> resposta de retorno enviada pelo serviço -> .... -> recebimento assíncrono -> processamento de resposta assíncrona -> ...

E observe que eu encaminhei vários caminhos de erro em potencial lá.


Depois de abordar algumas das suposições subjacentes, acho que fica mais claro por que suprimir discretamente as exceções é uma má idéia. Uma exceção e uma mensagem de erro associada é um indicador importante para os usuários de que algo deu errado. Mesmo que a mensagem não faça sentido para o usuário final, as informações incorporadas podem ser úteis para o desenvolvedor na compreensão do que deu errado. Se tiverem sorte, a mensagem chegará à solução.

Acho que parte do problema aqui é que uma exceção tratada incorretamente irá travar seu aplicativo. Suprimindo as exceções, o aplicativo não falha. Existe alguma validade para isso, já que o objetivo do assíncrono é permitir que o aplicativo continue trabalhando enquanto os dados estão sendo recuperados. Não é uma extensão lógica dizer que, se você continuar operando enquanto aguarda os resultados, uma falha na recuperação também não deve travar o aplicativo - a chamada assíncrona é implicitamente declarada como não sendo importante o suficiente para finalizar o aplicativo.

Portanto, é uma solução, mas está resolvendo o problema errado. Não é preciso muito esforço para fornecer um wrapper para o tratamento de exceções, para que o aplicativo possa permanecer em execução. A exceção é capturada, a mensagem de erro é lançada na tela ou registrada e o aplicativo pode continuar em execução. Suprimir a exceção implica que ignorar erros ficará OK e que seu teste garantirá que as exceções nunca escapem ao campo de qualquer maneira.

Portanto, minha avaliação final é que essa é uma tentativa incompleta de resolver um problema criado ao levar as pessoas a um modelo assíncrono quando não estavam prontas para mudar seu pensamento para essa abordagem.


fonte