It is important to know how exceptions are handled in an asynchronous program. Partly because these subtle points can sometime become a headache. When exception are thrown in a code that runs inside a task, all the exceptions are placed on the task object and returned to the calling thread. When exceptions happen, all the exceptions are re-thrown by the calling thread. To do that they’re wrapped inside
AggregateException and returned to the caller. So when we await a task, we only get the first exception from a collection of exceptions that might exist on a task. In this post I’m going to discuss these points and bring to your attention some important aspect of exceptions in async programs.
How Exceptions Handled in Async Program
There’s a difference between how a normal program handle and receive exceptions and how a task-based program does it. When an exception happens in a task based program all the exceptions are place on task object and returned. The important thing is if we need all exceptions we need to retrieve it. In other words if multiple exceptions happen, if we await the task in a normal way, we only going to get the latest exception. Let me clarify what I’ve said with an example.
In the example above, we’re not going to enter the
catch (AggregateException e) block. Instead we’re going to enter the
catch (Exception e) block and we only going to receive one of the exceptions. Although two exception happened when we run the program. So we only get the
NotImplementedException and not the
When using await, it’s going to unwrap the first exception and return it, that’s why we don’t hit the
catch (AggregateException e) line. But if we use something like the below code sample, we catch the
AggregateException, note that it’s not a good idea since we’re blocking the running thread.
Get All Exceptions From A Task
In the previous example we saw how other exceptions are ignored if we handle exceptions with tasks in a normal way. Now let’s see how we can retrieve all of them if we need to.
In the code above, we catch the exceptions from the Exception property and assign it to a variable of type
Note that this example is not about best practices regarding handling exceptions, but how we can retrieve all the exceptions from a task.
How to Deal With Nested AggregateExceptions
Imagine a scenario when a task contains another task, the inner task throws couple of exceptions. Now the parent task throws exceptions too, in this scenario, the
InnerExceptions property of the parent contains another
AggregateException. In this case we can use
Flatten() method to flatten all the child exceptions.
If you run the above code and examine the nestedExceptions and flatNestedExceptions variables, you’ll see how aggregate exception are flattened.
Treat Exceptions as Handled by AggregateException.Handle
You can use
AggregateException.Handle to handle certain exceptions and ignore them if they’re not harmful. The
AggregateException.Handle accept a delegate that returns a bool. If what’s returned is true, the exception is considered to be handled. But if it returns false, the exception re-thrown again after Handle ends. Let’s consider this code excerpt for example.
In this case, the exception of type
ArgumentNullException re-thrown after the Handle method finish executing.
What Happens to Exceptions When Using Async Void
There’s a lot of reasons why we shouldn’t use async void, but I’m going to show you what happens to exceptions when used this way. Take the below code for instance.
If you run this code, we never hit the line that catches the exception. Because the program cannot re-throw the exception in the calling thread and there’s no task object to place it on. Now let’s correct this program by using
Task instead of
void. If we run the program now, we see that we hit the lines that catches the exception.
In this post we saw how exceptions are handled differently when using task based APIs. We also saw some mistakes and gotchas to watch for when we dealing with exceptions in an asynchronous programs.