318 votes

Intercepter une exception levée par une méthode asynchrone

À l'aide de la async ctp à partir de Microsoft pour .NET, est-il possible d'intercepter une exception levée par une méthode asynchrone dans l'appel de la méthode?

public async void Foo()
{
    var x = await DoSomethingAsync();
    /* Handle the result, but sometimes an exception might be thrown
       For example, DoSomethingAsync get's 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
             when deployed the app will simply crash. */
    }
}

Donc, fondamentalement, je veux l'exception de la async code à bulles dans mon code appelant si c'est même possible à tous.

297voto

Stuart Points 45896

C'est un peu bizarre de lire (un esprit f*** est la façon dont je suis le trouver!) mais oui, l'exception de la bulle jusqu'à l'appel de code, mais il ne fera que faire si vous await l'appel à Foo

public async void DoFoo()
{
    try
    {
        await Foo();
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught because you've awaited the call. */
    }
}

Cette explication http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions est assez bonne, il décrit les mesures que le compilateur prend pour réaliser ce tour de magie.

80voto

Rob Church Points 1300

La raison de l'exception n'est pas pris est parce que les Foo() la méthode a un type de retour void et donc quand vous attendent est appelé, il retourne simplement. Comme DoFoo() n'est pas en attente de l'achèvement de Toto, le gestionnaire d'exception ne peut pas être utilisé.

Cela ouvre une solution plus simple si vous pouvez modifier les signatures de méthode - alter Foo() , de sorte qu'il renvoie type Task puis DoFoo() peut await Foo(), comme dans ce code:

public async Task Foo() {
    var x = await DoSomethingThatThrows();
}

public async void DoFoo() {
    try {
        await Foo();
    } catch (ProtocolException ex) {
        // This will catch exceptions from DoSomethingThatThrows
    }
}

20voto

Alois Kraus Points 6179

Votre code n'est pas ce que vous en pensez. Méthodes asynchrones sera de retour immédiatement après la méthode commence à attendre le async résultat. Pour voir ce qui se passe c'est éducatif pour ajouter un peu de suivi pour voir comment le fil de l'interaction.

Dans l'exemple suivant, je ne

  • Créer 4 tâches
  • Pour chaque tâche de manière asynchrone incrémenter un nombre et de retourner le nombre incrémenté
  • Lorsque l'async résultat est arrivé il s'est tracée.

    static TypeHashes _type = new TypeHashes(typeof(Program));        
    private void Run()
    {
        TracerConfig.Reset("debugoutput");
    
        using (Tracer t = new Tracer(_type, "Run"))
        {
            for (int i = 0; i < 4; i++)
            {
                DoSomeThingAsync(i);
            }
        }
        Application.Run();  // Start window message pump to prevent termination
    }
    
    
    private async void DoSomeThingAsync(int i)
    {
        using (Tracer t = new Tracer(_type, "DoSomeThingAsync"))
        {
            t.Info("Hi in DoSomething {0}",i);
    
            try
            {
                int result = await Calculate(i);
                t.Info("Got async result: {0}", result);
            }
            catch (ArgumentException ex)
            {
                t.Error("Got argument exception: {0}", ex);
            }
        }
    }
    
    
    Task<int> Calculate(int i)
    {
        var t = new Task<int>(() =>
        {
            using (Tracer t2 = new Tracer(_type, "Calculate"))
            {
                if( i % 2 == 0 )
                    throw new ArgumentException(String.Format("Even argument {0}", i));
                return i++;
            }
        });
        t.Start();
        return t;
    }
    

Lorsque vous observez les traces

22:25:12.649  02172/02820 {          AsyncTest.Program.Run 
22:25:12.656  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.657  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 0    
22:25:12.658  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.659  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.659  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 1    
22:25:12.660  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 2    
22:25:12.662  02172/02820 {          AsyncTest.Program.DoSomeThingAsync     
22:25:12.662  02172/02820 Information AsyncTest.Program.DoSomeThingAsync Hi in DoSomething 3    
22:25:12.664  02172/02756          } AsyncTest.Program.Calculate Duration 4ms   
22:25:12.666  02172/02820          } AsyncTest.Program.Run Duration 17ms  ---- Run has completed. The async methods are now scheduled on different threads. 
22:25:12.667  02172/02756 Information AsyncTest.Program.DoSomeThingAsync Got async result: 1    
22:25:12.667  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 8ms    
22:25:12.667  02172/02756 {          AsyncTest.Program.Calculate    
22:25:12.665  02172/05220 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 0   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.668  02172/02756 Exception   AsyncTest.Program.Calculate Exception thrown: System.ArgumentException: Even argument 2   
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     
22:25:12.724  02172/05220          } AsyncTest.Program.Calculate Duration 66ms      
22:25:12.724  02172/02756          } AsyncTest.Program.Calculate Duration 57ms      
22:25:12.725  02172/05220 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 0  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 106    
22:25:12.725  02172/02756 Error       AsyncTest.Program.DoSomeThingAsync Got argument exception: System.ArgumentException: Even argument 2  

Server stack trace:     
   at AsyncTest.Program.c__DisplayClassf.Calculateb__e() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 124   
   at System.Threading.Tasks.Task`1.InvokeFuture(Object futureAsObj)    
   at System.Threading.Tasks.Task.InnerInvoke()     
   at System.Threading.Tasks.Task.Execute()     

Exception rethrown at [0]:      
   at System.Runtime.CompilerServices.TaskAwaiter.EndAwait()    
   at System.Runtime.CompilerServices.TaskAwaiter`1.EndAwait()  
   at AsyncTest.Program.DoSomeThingAsyncd__8.MoveNext() in C:\Source\AsyncTest\AsyncTest\Program.cs:line 0      
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 70ms   
22:25:12.726  02172/02756          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   
22:25:12.726  02172/05220 {          AsyncTest.Program.Calculate    
22:25:12.726  02172/05220          } AsyncTest.Program.Calculate Duration 0ms   
22:25:12.726  02172/05220 Information AsyncTest.Program.DoSomeThingAsync Got async result: 3    
22:25:12.726  02172/05220          } AsyncTest.Program.DoSomeThingAsync Duration 64ms   

Vous remarquerez que la méthode de course complète sur le thread 2880 alors qu'un calcul a commencé sur un autre thread. Si vous mettez un try/catch autour de votre attendent méthode, vous pouvez "attraper" à l'exception de la manière habituelle, même si votre code est exécuté sur un autre fil de discussion lors de la tâche de calcul est terminé et votre contiuation est exécutée.

La méthode de calcul des traces à l'exception lancée automatiquement parce que je n'ai utiliser le ApiChange.Api.dll à partir de la ApiChangeoutil. Le traçage et le Réflecteur aide beaucoup à comprendre ce qui se passe. Pour se débarrasser de threading, vous pouvez créer vos propres versions de GetAwaiter BeginAwait et EndAwait et enveloppez pas une tâche, mais par exemple, un Paresseux et trace à l'intérieur de vos propres méthodes d'extension. Ensuite, vous aurez une bien meilleure compréhension de ce que le compilateur et ce que le TPL.

Maintenant, vous voyez qu'il n'y a aucun moyen d'obtenir dans un try/catch votre exception de retour depuis il n'y a pas de pile de gauche pour toute exception à se propager à partir. Votre code peut être en train de faire quelque chose de totalement différent après vous n'avez initier les opérations asynchrones. Il pourrait faire appel Thread.Dormir ou même de mettre fin. Tant qu'il y a un thread de premier plan à gauche de votre application sera heureux de continuer à exécuter des tâches asynchrones.


Vous pouvez gérer l'exception à l'intérieur de la méthode async après votre opération asynchrone a fait fini et de retour d'appel dans le thread de l'INTERFACE utilisateur. La méthode recommandée pour le faire est avec TaskScheduler.FromSynchronizationContext. Qui fonctionne seulement si vous avez un thread d'INTERFACE utilisateur et il n'est pas très occupé avec d'autres choses.

La vôtre, Alois Kraus

5voto

rohanjansen Points 1

Il est aussi important de noter que vous perdrez l'chronologique trace de la pile de l'exception, si vous avez un type de retour void sur une méthode asynchrone. Je vous recommandons de retourner Tâche comme suit. Va faire du débogage d'un ensemble beaucoup plus facile.

public async Task DoFoo()
    {
        try
        {
            return await Foo();
        }
        catch (ProtocolException ex)
        {
            /* Exception with chronological stack trace */     
        }
    }

4voto

L'exception peut être pris dans la fonction async.

public async void Foo()
{
    try
    {
        var x = await DoSomethingAsync();
        /* Handle the result, but sometimes an exception might be thrown
           For example, DoSomethingAsync get's data from the network
           and the data is invalid... a ProtocolException might be thrown */
    }
    catch (ProtocolException ex)
    {
          /* The exception will be caught here */
    }
}

public void DoFoo()
{
    Foo();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X