[c#] Catch an exception thrown by an async void method

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()
{
    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. */
    }
}

So basically I want the exception from the async code to bubble up into my calling code if that is even possible at all.

The answer is


The exception can be caught in the async function.

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();
}

This blog explains your problem neatly Async Best Practices.

The gist of it being you shouldn't use void as return for an async method, unless it's an async event handler, this is bad practice because it doesn't allow exceptions to be caught ;-).

Best practice would be to change the return type to Task. Also, try to code async all the way trough, make every async method call and be called from async methods. Except for a Main method in a console, which can't be async (before C# 7.1).

You will run into deadlocks with GUI and ASP.NET applications if you ignore this best practice. The deadlock occurs because these applications runs on a context that allows only one thread and won't relinquish it to the async thread. This means the GUI waits synchronously for a return, while the async method waits for the context: deadlock.

This behaviour won't happen in a console application, because it runs on context with a thread pool. The async method will return on another thread which will be scheduled. This is why a test console app will work, but the same calls will deadlock in other applications...


Its also important to note that you will lose the chronological stack trace of the exception if you you have a void return type on an async method. I would recommend returning Task as follows. Going to make debugging a whole lot easier.

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

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<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;
}

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.


The reason the exception is not caught is because the Foo() method has a void return type and so when await is called, it simply returns. As DoFoo() is not awaiting the completion of Foo, the exception handler cannot be used.

This opens up a simpler solution if you can change the method signatures - alter Foo() so that it returns type Task and then DoFoo() can await Foo(), as in this 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
    }
}

Examples related to c#

How can I convert this one line of ActionScript to C#? Microsoft Advertising SDK doesn't deliverer ads How to use a global array in C#? How to correctly write async method? C# - insert values from file into two arrays Uploading into folder in FTP? Are these methods thread safe? dotnet ef not found in .NET Core 3 HTTP Error 500.30 - ANCM In-Process Start Failure Best way to "push" into C# array

Examples related to asynchronous

How to read file with async/await properly? Use Async/Await with Axios in React.js Waiting until the task finishes How to reject in async/await syntax? React - Display loading screen while DOM is rendering? angular 2 how to return data from subscribe How do I access store state in React Redux? SyntaxError: Unexpected token function - Async Await Nodejs Why does .json() return a promise? Why is setState in reactjs Async instead of Sync?

Examples related to exception-handling

Catching FULL exception message Spring Resttemplate exception handling How to get exception message in Python properly Spring Boot REST service exception handling java.net.BindException: Address already in use: JVM_Bind <null>:80 Python FileNotFound The process cannot access the file because it is being used by another process (File is created but contains nothing) Java 8: Lambda-Streams, Filter by Method with Exception Laravel view not found exception How to efficiently use try...catch blocks in PHP

Examples related to async-await

How to correctly write async method? How can I use async/await at the top level? Any difference between await Promise.all() and multiple await? Async/Await Class Constructor Syntax for async arrow function try/catch blocks with async/await Using filesystem in node.js with async / await Use async await with Array.map Using await outside of an async function SyntaxError: Unexpected token function - Async Await Nodejs

Examples related to task-parallel-library

What is the difference between Task.Run() and Task.Factory.StartNew() Task.Run with Parameter(s)? HttpClient - A task was cancelled? Running multiple async tasks and waiting for them all to complete Deserialize JSON to Array or List with HTTPClient .ReadAsAsync using .NET 4.0 Task pattern Calling async method synchronously How can I tell Moq to return a Task? When to use Task.Delay, when to use Thread.Sleep? Awaiting multiple Tasks with different results How to safely call an async method in C# without await