Async Error Handling recap

Damir Dobric Posts

Next talks:

 

    

Follow me on Twitter: #ddobric



 

 

Archives

When working with async you will probable immediately notice that async programming is much easier then before. Unfortunately after a while you might notice, that that some things like ExecptionHandling are night mare now.
In fact “difficult things became easier, but easy things evaluated to more complicated”. In this post I want to shortly recap some scenarios which every .NET developer should now.


Exception Handling on async void methods

One of hello world examples which nicely shows the beginning of the hardness is throwing of errors from async method which has no return parameters.
A trivial example of a such method is:

private async void doDomething()

In theory this method is an event because compiler will split its code in synchrony and asynchrony part. The second one behaves as an event, which has no and should not have return parameters.
Following code snippet shows a solution which unsuccessfully tries to handle exceptions:

 


public
class ThrowFromAsyncVoidMethod
   {
      
private async void doWithFailure()
       {
          
throw new InvalidOperationException();
       }


      
public void Go()
       {
          
try
           {
               doWithFailure();
           }
          
catch (Exception)
           {
              
// This handler cannot handle exceptions thrown inside of async method.
              
throw;
           }
       }}


The console application invokes this code as follows:

//
// This demonstrates that it is not possible to catch exceptions on
// async void methods.
ThrowFromAsyncVoidMethod sample1 = new ThrowFromAsyncVoidMethod();
sample1.Go();


 

Result:

Start this code and you will notice that exception is not handled. The application will crash.
Unfortunately if this happen in one of hundreds of such methods there is no chance to find out where it has happen.
Following picture shows what you will typically see when that happens.

image

Take a look in the stack trace and notice that there is no your code in there (Visual Studio 2013 will handle this a bit better).

   at Daenet.AsyncExceptionHandling.ThrowFromAsyncVoidMethod.<doWithFailure>d__0.MoveNext() in c:\Temp\Daenet.AsyncExceptionHandling\Daenet.AsyncExceptionHandling\01ThrowFromAsyncVoidMethod.cs:line 12
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__1(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()


The exception handler cathc(Exception ex) catches exceptions thrown in process of start of async method.
Because of that there is no really any exception handler which will handle this exception. The only place where you can handle such exceptions is event:

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;

For example, the MSTest asynchronous testing support only works for async methods returning Task. It is also possible to install a SynchronizationContext that detects completion of all async void methods have and collects exceptions

Conculsion:

Async Void methods are events without return parameter and as such, very difficult to handle. You should rather use async Task methods.
If so why should you use such methods? The problem is that such methods are mostly generated by visual studio as events. For example following method is
the main entry point of every Windows Store App:

protected override async void OnLaunched(LaunchActivatedEventArgs args)


1. Try using async methods which return Task instead of void
2. The only place where you can collect such exceptions is event UnhandledException.

Take a look in the stack trace and notice that there is no your code in there.

   at Daenet.AsyncExceptionHandling.ThrowFromAsyncVoidMethod.<doWithFailure>d__0.MoveNext() in c:\Temp\Daenet.AsyncExceptionHandling\Daenet.AsyncExceptionHandling\01ThrowFromAsyncVoidMethod.cs:line 12
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.CompilerServices.AsyncMethodBuilderCore.<ThrowAsync>b__1(Object state)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
   at System.Threading.QueueUserWorkItemCallback.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
   at System.Threading.ThreadPoolWorkQueue.Dispatch()


The exception handler catch(Exception ex) in our sample above catches exceptions thrown in process of start of a method.
Because of that there is no really any exception handler which will handle this exception if thrown within async method. The only place where you can handle such exceptions is event:

AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;


For example, the MSTest asynchronous testing support only works for async methods which are returning Task. It is also possible to install a SynchronizationContext that detects completion of all async void methods have and collects exceptions

Conculsion:

Async Void methods are events without return parameter and as such, very difficult to handle. You should rather use async methods which return Task instead of void.
If so why should you use such methods? The problem is that such methods are mostly generated automatically by visual studio as events. For example following method is
the main entry point of every Windows Store App:

protected override async void OnLaunched(LaunchActivatedEventArgs args)


You can put her async, but there is not place which will handle this exception if thrown from some async peace of your implementation inside of this method.


1. Try using async methods which return Task instead of void
2. The only place where you can collect such exceptions is event UnhandledException.
3. If you cannot use async (for example methods are generated automatically), then handle properly exceptions inside of async method (see below – next topic describes this).

 

Exception Handling on async task methods

We can now slightly change the previous example to return a Task instead of void. Following code snippet shows a solution which also unsuccessfully tries to handle exceptions:

 

public class ThrowFromAsyncTaskMethod
   {
      
private async Task doWithFailure()
       {
          
throw new InvalidOperationException();
       }


      
public void Go ()
       {
          
try
           {
               doWithFailure();
           }
          
catch (Exception)
           {   
              
// This will not catch exceptions thrown inside of async method.
              
// It will only catch exceptions related to failures which
               // happen during starting of the task.

              
throw;
           }
       }
}

 

//
// This domonstrates that it is not possible to catch exceptions on
// async void methods.

ThrowFromAsyncTaskMethod sample1 = new ThrowFromAsyncTaskMethod();
sample1.Go();


Result

This time we are returning a task instead of void, but again exception handler does not handle exceptions which are thrown inside of async method.
However this time CurrentDomain_UnhandledException will not be invoked at all and application will not crash. The only problem with this is that you have no glue that exception is thrown.
This is one of worst cases which can happen.
Fortunately we can use ContinueWith() to test for failures.

 

public class ThrowFromAsyncTaskMethod
   {
      
private async Task doWithFailure()
       {
          
throw new InvalidOperationException();
       }


      
public void Go ()
       {
          
try
           {              

                doWithFailure().ContinueWith((t) =>
                {
                   
if (t.Exception != null)
                    {
                       
Console.ForegroundColor = ConsoleColor.Yellow;
                       
Console.WriteLine(t.Exception + " " + t.Exception.Message);
                    }
                });}


           }
          
catch (Exception)
           {   
              
// This will not catch exceptions thrown inside of async method.
              
// It will only catch exceptions related to failures which
               // happen during starting of the task.

              
throw;
           }
       }
}

 

Conclusion

Because async task methods return a parameter they behave almost as events with return parameters. This sounds to be against few event conventions, but let’s express it like  “who cares”.
Due existence of return parameter we can grab collected information about completed task and perform some action.

1. To handle such exceptions event UnhandledException cannot be used.
2. You can use ContinueWith() to grab out exception. The issue with ContinueWith is that it is not executed on the UI thread. If you are touching model members inside of this method, it will throw.

Wait throws Aggregated Exceptions

In this scenario we assume that we understand why it is better to use Async Task methods instead of Async Void methods. So, samples shown in this scenarios are of type Async Task.
Now I will describe using of Wait() when handling exceptions. Remember in previous scenario we have used ContinueWith() to grab out exception.

 

 public void  GoWait()
        {
           
try
            {
               
Task t = doWithFailure();
                t.Wait();
            }
           
catch (AggregateException e)
            {
               
// This handler CAN handle exceptions thrown inside of async method.
               
// This is executed after Wait() has completed.

               
Console.ForegroundColor = ConsoleColor.Yellow;
               
Console.WriteLine(e + " " + e.Message);
            }
        }

Result

This time exception handler can handle exceptions. The thrown exception is always of type AggregateException. This exception will hold the list of specific exceptions.

Conclusion

1. Exception handler can be used to catch aggregated exceptions.
2. Wait should/can be used if you want to stop propagation of async pattern to upper stack.

await throws specific Exceptions

In this scenario we we will focus using of await in context of exception handling. In general there is nothing bad with Wait() which throws AggregateException. However it is sometimes more natural to handle
specific exceptions instead of some wrapped one l(ike AggregateException). In such cases you should/can use await.

 public async  void  GoAWait()
        {
           
try
            {
               
await doWithFailure();
            }
           
catch (Exception e)
            {
               
// This handler CAN handle exceptions thrown inside of async method.
               
// This is executed after await has completed.

               
Console.ForegroundColor = ConsoleColor.Yellow;
               
Console.WriteLine(e + " " + e.Message);
            }
        }

Result

Exception handler can handle exceptions. The thrown exception is specific exception like InvalidOperationException

Conclusion

1. await is suitable to handle specific exceptions.
2. await can be used to propagate async pattern to upper stack


At the end I hope this helps you to more understand what happened behind async  pattern in context of exception handling. I think these 4 scenarios cover most of requirements which a typical application will require.
Unfortunately hunting of exceptions on the stack is still very hard and almost impossible, as usually when programming multithreaded applications. I would love to see in the future tools which visualize “connection” between sync part of method which has invoked its async part (on the stack).

Download Sample: http://code.msdn.microsoft.com/Exception-Handling-and-84b500c2


Posted Jul 30 2013, 07:44 AM by Damir Dobric
developers.de is a .Net Community Blog powered by daenet GmbH.