Catch an exception thrown by an async void method

前端 未结 6 1489
北恋
北恋 2020-11-22 11:20

Using the async CTP from Microsoft for .NET, is it possible to catch an exception thrown by an async method in the calling method?

public async void Foo()
{
         


        
6条回答
  •  长情又很酷
    2020-11-22 11:44

    Your code doesn't do what you might think it does. Async methods return immediately after the method begins waiting for the async result. It's insightful to use tracing in order to investigate how the code is actually behaving.

    The code below does the following:

    • Create 4 tasks
    • Each task will asynchronously increment a number and return the incremented number
    • When the async result has arrived it is traced.

     

    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 Calculate(int i)
    {
        var t = new Task(() =>
        {
            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;
    }
    

    When you observe the 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   
    

    You will notice that the Run method completes on thread 2820 while only one child thread has finished (2756). If you put a try/catch around your await method you can "catch" the exception in the usual way although your code is executed on another thread when the calculation task has finished and your contiuation is executed.

    The calculation method traces the thrown exception automatically because I did use the ApiChange.Api.dll from the ApiChange tool. Tracing and Reflector helps a lot to understand what is going on. To get rid of threading you can create your own versions of GetAwaiter BeginAwait and EndAwait and wrap not a task but e.g. a Lazy and trace inside your own extension methods. Then you will get much better understanding what the compiler and what the TPL does.

    Now you see that there is no way to get in a try/catch your exception back since there is no stack frame left for any exception to propagate from. Your code might be doing something totally different after you did initiate the async operations. It might call Thread.Sleep or even terminate. As long as there is one foreground thread left your application will happily continue to execute asynchronous tasks.


    You can handle the exception inside the async method after your asynchronous operation did finish and call back into the UI thread. The recommended way to do this is with TaskScheduler.FromSynchronizationContext. That does only work if you have an UI thread and it is not very busy with other things.

提交回复
热议问题