24

When an async method that is awaited upon throws an exception, the exception is stored somewhere and throwing it is delayed. In a WinForms or WPF application, it uses SynchronizationContext.Current to post throwing of the exception. However, in e.g. a console application, it throws the exception on a thread pool and it brings down the application.

How can I prevent exceptions thrown from an async method from bringing down the application?

EDIT:

Appearantly the issue I'm describing is because I have void async methods. See comments.

Pieter van Ginkel
  • 29,160
  • 8
  • 71
  • 111
  • 2
    If an async method is awaited, the exception is thrown in the code that awaits it. Unhandled exceptions behave this way only if the method is `void` returning. This is a reason to avoid using `async void` methods as much as possible. – svick Sep 07 '12 at 10:41
  • 1
    @svick `async void` methods result in deterministic behaviour; calling a `Task`-returning function which results in an exception and not awaiting the task will raise the `UnobservedTaskException` event at some semirandom point in the future when the garbage collector runs, and if that does nothing, lets the program silently continue as if everything's all right. The problem isn't with `async void` methods, they merely expose the real problem. If you *don't* call a `Task`-returning function from an `async` method, there's a good chance you're doing something wrong. –  Sep 07 '12 at 11:53
  • By the way, only "good chance" because there are a few exceptional (no pun intended) cases where it's okay to discard exceptions, but not a whole lot. –  Sep 07 '12 at 11:55
  • @hvd: Event handlers must be void. Why isn't the `UnobservedTaskException` an issue there? – Pieter van Ginkel Sep 07 '12 at 12:18
  • @hvd: I think `async Task` embraces the *naturally nondeterministic* nature of asynchronous programming. With `async void` you still get an exception at some semirandom point in the future, one that is (usually) passed directly to your main loop. I think the code is cleaner if you use `async Task` and ensure they are `await`ed. IMO. :) – Stephen Cleary Sep 07 '12 at 12:42
  • @Pieter `async void` methods observe the exception, by posting it to the synchronisation context. `async void f() { await g(); }` is fine. `void f() { var task = g(); /* ignore task */ }` is not. –  Sep 07 '12 at 13:48
  • @StephenCleary It's not a precisely determined point in the future, but it's not random either, unlike with garbage collection. :) And at one point, a non-task-returning function will start an async operation, and that one non-task-returning function should usually be an `async void` method. I think you mean that that one method should be as high up as possible, everything else should just return a task. If so, sure, no argument there. –  Sep 07 '12 at 13:55
  • 1
    @hvd: OK, not "random". :) I disagree about the necessity of `async void`, though. You'll have one if you need an `async` event handler (usually in UI apps), but not otherwise. ASP.NET WebAPI and MVC give you most top-level entry points as `async Task`. For the op's situation (console apps), I still prefer `async Task` to `async void`. – Stephen Cleary Sep 07 '12 at 18:20
  • @StephenCleary Ah, sure, when the topmost async method is hidden in a library you're using, you can avoid having the `async void` in your own code. I didn't know that existed, nice, thanks for the info. FWIW, what I do in my own code is use an `async void` method that does nothing more than awaiting another task, catching the specific exception types that are safe to ignore (`OperationCanceledException`, mainly), and doing nothing with them. All the benefits of both of the options. –  Sep 07 '12 at 18:42

2 Answers2

22

How can I prevent exceptions thrown from an async method from bringing down the application?

Follow these best practices:

  1. All async methods should return Task or Task<T> unless they have to return void (e.g., event handlers).
  2. At some point, you should await all Tasks returned from async methods. The only reason you wouldn't want to do this is if you no longer care about the result of the operation (e.g., after you cancel it).
  3. If you need to catch an exception from an async void event handler, then catch it in the event handler - exactly like you would do if this was synchronous code.

You may find my async / await intro post helpful; I cover several other best practices there as well.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    I understand that the problem I'm getting is that I have `void` `async` methods. However, does this means that the issue that I'm describing does occur for event handlers? – Pieter van Ginkel Sep 07 '12 at 12:24
  • Yes; to reiterate the third point, if you have an `async void` event handler, you probably want to catch exceptions *within* that event handler. A synchronous event handler will have the exact same problem - it will crash the app if an exception escapes while it's running on a thread pool thread. – Stephen Cleary Sep 07 '12 at 12:29
  • Except that the exceptions are routed to the `SynchronizationContext` that was captured when the `async` method was invoked. In a WinForms/WPF application most of the time this will be a correct synchronization context since the event will most likely have been started from the UI thread. It looks like the behavior isn't that different from what happens when the event isn't `async`. – Pieter van Ginkel Sep 07 '12 at 12:32
  • 1
    The `SynchronizationContext` routing is done to emulate the synchronous event handler behavior. Exceptions from both synchronous event handlers and `async void` event handlers end up in the context. This is true whether the context is UI, ASP.NET, or thread pool. – Stephen Cleary Sep 07 '12 at 12:36
  • Thanks! Thread Synchronization might solve the problem but this is the right\intended way. – Rushui Guan Sep 21 '13 at 04:59
13

When the async method is started, it captures the current synchronization context. A way to solve this issue is to create your own synchronization context which captures the exception.

The point here is that the synchronization context posts the callback to the thread pool, but with a try/catch around it:

public class AsyncSynchronizationContext : SynchronizationContext
{
    public override void Send(SendOrPostCallback d, object state)
    {
        try
        {
            d(state);
        }
        catch (Exception ex)
        {
            // Put your exception handling logic here.

            Console.WriteLine(ex.Message);
        }
    }

    public override void Post(SendOrPostCallback d, object state)
    {
        try
        {
            d(state);
        }
        catch (Exception ex)
        {
            // Put your exception handling logic here.

            Console.WriteLine(ex.Message);
        }
    }
}

In the catch above you can put your exception handling logic.

Next, on every thread (SynchronizationContext.Current is [ThreadStatic]) where you want to execute async methods with this mechanism, you must set the current synchronization context:

SynchronizationContext.SetSynchronizationContext(new AsyncSynchronizationContext());

The complete Main example:

class Program
{
    static void Main(string[] args)
    {
        SynchronizationContext.SetSynchronizationContext(new AsyncSynchronizationContext());

        ExecuteAsyncMethod();

        Console.ReadKey();
    }

    private static async void ExecuteAsyncMethod()
    {
        await AsyncMethod();
    }

    private static async Task AsyncMethod()
    {
        throw new Exception("Exception from async");
    }
}
Pieter van Ginkel
  • 29,160
  • 8
  • 71
  • 111
  • 5
    I'm sorry but I don't see where exactly does your SynchronizationContext post anything on the thread pool - as far as I can see all it does is it executes the function immediately on the calling thread and returns. – Rafael Munitić May 23 '13 at 22:07