Exception Handling in Async Code
Exceptions in async code behave a little differently from synchronous code, especially once multiple tasks are involved.
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
try
{
await RiskyAsync();
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Caught: {ex.Message}");
}
}
static async Task RiskyAsync()
{
await Task.Delay(500);
throw new InvalidOperationException("Something went wrong");
}
}
The pieces
- A regular
try/catcharound anawaitworks exactly like you'd hope, an exception thrown inside the awaited async method surfaces at theawaitpoint, as if it were a normal synchronous call. - With
Task.WhenAll, if multiple tasks fail, only the first exception is rethrown by theawait, the rest are still available on each individualTask.Exceptionproperty (anAggregateException) if you need them.
Task t1 = FailAsync("A");
Task t2 = FailAsync("B");
try
{
await Task.WhenAll(t1, t2);
}
catch
{
Console.WriteLine(t1.Exception?.InnerException?.Message);
Console.WriteLine(t2.Exception?.InnerException?.Message);
}
- An exception thrown inside an
async voidmethod (instead ofasync Task) can't be caught by a surroundingtry/catchat all, it escapes straight to the app's global unhandled-exception handler, another reason to avoidasync voidoutside of event handlers.
Best practice
Catch specific exception types close to where they can meaningfully be handled, exactly like synchronous code, async/await doesn't change the fundamentals of exception handling, it just changes when the exception surfaces (at the await, not at the original call).