Ma bibliothèque de tâches asynchrones doit-elle avaler des exceptions tranquillement?

10

Je viens d'apprendre que .NET 4.5 a introduit un changement dans la gestion des exceptions à l'intérieur d'un Task. À savoir, ils sont discrètement supprimés.

Le raisonnement officiel pour expliquer pourquoi cela a été fait semble être "nous voulions être plus amicaux avec les développeurs inexpérimentés":

Dans .NET 4.5, les tâches ont beaucoup plus d'importance que dans .NET 4, car elles sont intégrées aux langages C # et Visual Basic dans le cadre des nouvelles fonctionnalités asynchrones prises en charge par les langages. En effet, cela déplace les tâches hors du domaine des développeurs expérimentés dans le domaine de tout le monde. En conséquence, cela conduit également à un nouvel ensemble de compromis sur la sévérité de la gestion des exceptions.

( source )

J'ai appris à croire que de nombreuses décisions dans .NET ont été prises par des gens qui savent vraiment ce qu'ils font, et il y a généralement une très bonne raison derrière les décisions qu'ils prennent. Mais celui-ci m'échappe.

Si je concevais ma propre bibliothèque de tâches asynchrones, quel est l'avantage d'avaler des exceptions que les développeurs du Framework n'ont pas vues?

Roman Starkov
la source
4
+1. Bonne question, étant donné que la non-propagation d'exceptions que vous ne pouvez pas gérer est une mauvaise pratique, même en dehors du contexte asynchrone. De plus, l'argument des développeurs inexpérimentés est assez boiteux, à mon humble avis. Quoi de plus gênant pour les débutants qu'un morceau de code qui non seulement ne fonctionne pas, mais qui ne lève aucune exception?
Arseni Mourzenko
@MainMa Exactement mes pensées, mais j'ai eu tort avant (quand il s'est avéré qu'elles avaient, en fait, une très bonne raison mais pas évidente), alors j'ai pensé que je demanderais.
Roman Starkov
2
Wow, je suis content que vous ayez posté ça juste parce que c'est la première fois que j'en entends parler; et il viole définitivement POLA . Vous avez raison de dire que ces gars savent vraiment ce qu'ils font, alors je suis sincèrement curieux de savoir quel raisonnement pourrait être derrière cela ... Je me demande si cela a une raison plus technique basée sur la mise en œuvre d'Async et comment les exceptions se propageraient comme fond étant passé à une coroutine ..
Jimmy Hoffa

Réponses:

2

Pour ce que ça vaut, le document auquel vous avez lié donne un exemple de cas comme justification:

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

Dans ce code, le développeur lance deux opérations asynchrones pour s'exécuter en parallèle, puis attend chacune de manière asynchrone à l'aide de la nouvelle fonctionnalité de langage d'attente ... [C] examine ce qui se passera si op1 et op2 sont en panne. Attendre op1 propagera l'exception d'op1, et donc op2 ne sera jamais attendu. Par conséquent, l'exception op2 ne sera pas observée, et le processus se terminera éventuellement.

Pour permettre aux développeurs d'écrire plus facilement du code asynchrone basé sur des tâches, .NET 4.5 modifie le comportement d'exception par défaut pour les exceptions non observées. Bien que les exceptions non observées provoquent toujours le déclenchement de l'événement UnobservedTaskException (ne pas le faire serait un changement de rupture), le processus ne se bloquera pas par défaut. Au lieu de cela, l'exception finira par être mangée après que l'événement soit déclenché, que le gestionnaire d'événements observe ou non l'exception.

Je n'en suis pas convaincu. Il supprime la possibilité d'une erreur non ambiguë, mais difficile à tracer (mystérieux plantage du programme qui peut se produire longtemps après l'erreur réelle), mais la remplace par la possibilité d'une erreur complètement silencieuse - qui pourrait devenir un problème tout aussi difficile à tracer plus tard. dans votre programme. Cela me semble un choix douteux.

Le comportement est configurable - mais bien sûr, 99% des développeurs vont simplement utiliser le comportement par défaut, sans jamais penser à ce problème. Donc, ce qu'ils ont sélectionné par défaut est un gros problème.


la source
Mais vous faites d' obtenir une exception normale op1, non? Si celui-ci reste non géré, il fera baisser le processus, non?
Timwi
1
@Timwi, si je comprends bien, ni op1l'exception ni op2l'exception ne feraient tomber le programme. L'avantage est que vous avez la possibilité d'observer les deux. Mais si vous ne le faites pas, ils seront tous les deux avalés. Je pourrais toutefois avoir tord.
Je suis vraiment surpris qu'ils trouvent si important de vous laisser "observer" les deux. Si vous voulez les "observer", vous les attrapez et signalez leur occurrence via le résultat de la tâche! ... D'accord avec votre raisonnement, +1
Roman Starkov
2

TL; DR - Non , vous ne devez pas simplement ignorer les exceptions tranquillement.


Regardons les hypothèses.

  • Votre foi aveugle

J'ai appris à croire que de nombreuses décisions dans .NET ont été prises par des gens qui savent vraiment ce qu'ils font, et il y a généralement une très bonne raison derrière les décisions qu'ils prennent. Mais celui-ci m'échappe.

MS a sans aucun doute des gens vraiment brillants. Ils ont également un certain nombre de gestionnaires et de cadres qui sont ... sans aucune idée, et qui prennent constamment de mauvaises décisions. Autant que nous aimerions croire le conte de fées du scientifique techniquement avisé qui dirige le développement du produit, la réalité est bien plus sombre.

Je veux dire que si votre instinct vous dit que quelque chose ne va pas, il y a de fortes chances (et un précédent passé) que ce soit faux.

  • Que c'est un problème technique

La façon de gérer les exceptions dans ce cas est une décision de facteurs humains, pas une décision technique. En gros, une exception est une erreur. La présentation de l'erreur, même si elle est mal formatée, fournit des informations critiques à l'utilisateur final. À savoir, quelque chose s'est mal passé.

Considérez cette conversation hypothétique entre un utilisateur final et un développeur.

Utilisateur: l'application est cassée.
Dev: Qu'est-ce qui est cassé?
Utilisateur: je ne sais pas, je clique sur le bouton et rien ne se passe.
Dev: Que voulez-vous dire par que rien ne se passe?
Utilisateur: Ecoute, je clique, j'attends, et j'attends, et j'attends, et rien ... c'est évidemment cassé.

Notre pauvre développeur, heureusement hypothétique, doit maintenant comprendre ce qui s'est mal passé dans la chaîne des événements. Où était-il?
Notification du gestionnaire d'événements -> Routine pour gérer l'événement -> Méthode déclenchée par le gestionnaire -> début de l'appel asynchrone -> les 7 couches du réseau OSI -> transmission physique -> sauvegarder les 7 couches du réseau OSI -> service de réception -> méthode appelée par service -> ... -> retour de réponse envoyé par service -> .... -> réception asynchrone -> traitement de la réponse asynchrone -> ...

Et veuillez noter que j'ai glissé plusieurs chemins d'erreur potentiels là-bas.


Après avoir abordé certaines des hypothèses sous-jacentes, je pense qu'il devient plus clair pourquoi la suppression silencieuse des exceptions est une mauvaise idée. Une exception et un message d'erreur associé est un indicateur clé pour les utilisateurs que quelque chose s'est mal passé. Même si le message n'a pas de sens pour l'utilisateur final, les informations intégrées peuvent être utiles au développeur pour comprendre ce qui n'a pas fonctionné. S'ils sont chanceux, le message mènera même vers la solution.

Je pense qu'une partie du problème ici est qu'une exception mal gérée plantera votre application. En supprimant les exceptions, l'application ne se bloquera pas. Il y a cependant une certaine validité dans ce sens, car le but de l'async est de permettre à l'application de continuer à fonctionner pendant la récupération des données. Ce n'est pas vraiment une extension logique de dire que si vous pouviez continuer à fonctionner en attendant les résultats, un échec de récupération ne devrait pas non plus bloquer l'application - l'appel asynchrone est implicitement déclaré comme n'étant pas suffisamment important pour mettre fin à l'application.

C'est donc une solution, mais elle résout le mauvais problème. Il ne faut pas autant d'efforts pour fournir un wrapper à la gestion des exceptions afin que l'application puisse continuer à fonctionner. L'exception est interceptée, un message d'erreur est affiché à l'écran ou enregistré et l'application est autorisée à continuer de fonctionner. La suppression de l'exception implique que l'ignorance des erreurs sera A-OK et que vos tests garantiront que les exceptions n'échapperont jamais au champ de toute façon.

Donc, mon évaluation finale est qu'il s'agit d'une tentative à moitié cuite de résoudre un problème créé en poussant les gens dans un modèle asynchrone lorsqu'ils n'étaient pas prêts à changer leur façon de penser à cette approche.


la source