À l'aide du CTP asynchrone de Microsoft pour .NET, est-il possible d'attraper une exception levée par une méthode async dans la méthode appelante?
public async void Foo()
{
var x = await DoSomethingAsync();
/* Handle the result, but sometimes an exception might be thrown.
For example, DoSomethingAsync gets data from the network
and the data is invalid... a ProtocolException might be thrown. */
}
public void DoFoo()
{
try
{
Foo();
}
catch (ProtocolException ex)
{
/* The exception will never be caught.
Instead when in debug mode, VS2010 will warn and continue.
The deployed the app will simply crash. */
}
}
Donc, fondamentalement, je veux que l'exception du code asynchrone se propage dans mon code d'appel si c'est même possible.
Réponses:
C'est un peu bizarre à lire, mais oui, l'exception va remonter jusqu'au code d'appel - mais seulement si vous
await
ouWait()
l'appel àFoo
.Notez que l'utilisation de Wait () peut entraîner le blocage de votre application si .Net décide d'exécuter votre méthode de manière synchrone.
Cette explication http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions est assez bonne - elle décrit les étapes que le compilateur prend pour réaliser cette magie.
la source
await
l'appel à Foo, quand Foo est de retour nul?async void Foo()
.Type void is not awaitable
?La raison pour laquelle l'exception n'est pas interceptée est que la méthode Foo () a un type de retour void et donc lorsque l'attente est appelée, elle revient simplement. Comme DoFoo () n'attend pas la fin de Foo, le gestionnaire d'exceptions ne peut pas être utilisé.
Cela ouvre une solution plus simple si vous pouvez modifier les signatures de méthode - modifiez de
Foo()
sorte qu'il retourne le typeTask
et puisDoFoo()
pouvezawait Foo()
, comme dans ce code:la source
Votre code ne fait pas ce que vous pourriez penser. Les méthodes asynchrones reviennent immédiatement après que la méthode commence à attendre le résultat asynchrone. Il est judicieux d'utiliser le traçage afin d'étudier le comportement réel du code.
Le code ci-dessous fait ce qui suit:
Quand on observe les traces
Vous remarquerez que la méthode Run se termine sur le thread 2820 alors qu'un seul thread enfant est terminé (2756). Si vous mettez un try / catch autour de votre méthode d'attente, vous pouvez "intercepter" l'exception de la manière habituelle bien que votre code soit exécuté sur un autre thread lorsque la tâche de calcul est terminée et que votre contiuation est exécutée.
La méthode de calcul trace automatiquement l'exception levée car j'ai utilisé ApiChange.Api.dll à partir de l' outil ApiChange . Le traçage et le réflecteur aident beaucoup à comprendre ce qui se passe. Pour vous débarrasser du filetage, vous pouvez créer vos propres versions de GetAwaiter BeginAwait et EndAwait et encapsuler non pas une tâche mais par exemple un paresseux et tracer à l'intérieur de vos propres méthodes d'extension. Ensuite, vous comprendrez mieux ce que le compilateur et ce que fait le TPL.
Vous voyez maintenant qu'il n'y a aucun moyen de récupérer / essayer votre exception, car il n'y a plus de cadre de pile pour la propagation d'une exception. Votre code peut faire quelque chose de totalement différent après avoir lancé les opérations asynchrones. Il peut appeler Thread.Sleep ou même se terminer. Tant qu'il reste un thread de premier plan, votre application continuera volontiers à exécuter des tâches asynchrones.
Vous pouvez gérer l'exception à l'intérieur de la méthode async une fois votre opération asynchrone terminée et rappeler dans le thread d'interface utilisateur. La méthode recommandée pour ce faire est avec TaskScheduler.FromSynchronizationContext . Cela ne fonctionne que si vous avez un thread d'interface utilisateur et qu'il n'est pas très occupé avec d'autres choses.
la source
L'exception peut être interceptée dans la fonction asynchrone.
la source
Il est également important de noter que vous perdrez la trace chronologique de la pile de l'exception si vous avez un type de retour vide sur une méthode asynchrone. Je recommanderais de retourner la tâche comme suit. Va rendre le débogage beaucoup plus facile.
la source
return
instruction, ce code fonctionne cependant, puisque leTask
est "implicitement" renvoyé en utilisantasync / await
.Ce blog explique clairement votre problème Async Best Practices .
L'essentiel étant que vous ne devriez pas utiliser void comme retour pour une méthode async, sauf s'il s'agit d'un gestionnaire d'événement async, c'est une mauvaise pratique car elle ne permet pas de détecter les exceptions ;-).
La meilleure pratique serait de changer le type de retour en tâche. Essayez également de coder async à fond, effectuez chaque appel de méthode async et soyez appelé à partir des méthodes async. Sauf pour une méthode Main dans une console, qui ne peut pas être asynchrone (avant C # 7.1).
Vous rencontrerez des blocages avec les applications GUI et ASP.NET si vous ignorez cette meilleure pratique. Le blocage se produit car ces applications s'exécutent dans un contexte qui n'autorise qu'un seul thread et ne le renoncent pas au thread asynchrone. Cela signifie que l'interface graphique attend de manière synchrone un retour, tandis que la méthode async attend le contexte: blocage.
Ce comportement ne se produira pas dans une application console, car il s'exécute sur le contexte avec un pool de threads. La méthode async reviendra sur un autre thread qui sera planifié. C'est pourquoi une application de console de test fonctionnera, mais les mêmes appels se bloqueront dans d'autres applications ...
la source