Catch an Exception Thrown by an Async Void Method

Catch an exception thrown by an async void method

It's somewhat weird to read but yes, the exception will bubble up to the calling code - but only if you await or Wait() the call to Foo.

public async Task Foo()
{
var x = await DoSomethingAsync();
}

public async void DoFoo()
{
try
{
await Foo();
}
catch (ProtocolException ex)
{
// The exception will be caught because you've awaited
// the call in an async method.
}
}

//or//

public void DoFoo()
{
try
{
Foo().Wait();
}
catch (ProtocolException ex)
{
/* The exception will be caught because you've
waited for the completion of the call. */
}
}

As Stephen Cleary wrote in Async/Await - Best Practices in Asynchronous Programming:

Async void methods have different error-handling semantics. When an exception is thrown out of an async Task or async Task method, that exception is captured and placed on the Task object. With async void methods, there is no Task object, so any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started.

Note that using Wait() may cause your application to block, if .NET decides to execute your method synchronously.

This explanation http://www.interact-sw.co.uk/iangblog/2010/11/01/csharp5-async-exceptions is pretty good - it discusses the steps the compiler takes to achieve this magic.

Exception handling of async void methods


Why is the exception not caught by the catch statement?

Because it's async void.

Can somebody explain the internals of exception handling of async void methods?

The Task in an async Task method represents the execution of that method. So when an async Task method raises an exception, that exception is used to fault the task.

void is an unnatural return type for async methods. Among other issues, there is nowhere to place the exception thrown by an async void method. So, any exceptions thrown out of an async void method will be raised directly on the SynchronizationContext that was active when the async void method started executing.

Is there a way to catch exceptions for async void methods?

You could provide your own SynchronizationContext.

But then, exceptions in event handlers will not be handled?

The "raise exceptions on the SynchronizationContext" behavior is intended to imitate the way exceptions work for event handlers. E.g. for a GUI application, the exception raised by a GUI event (Button_Click) will be forwarded to the same top-level application error handler (Application.DispatcherUnhandledException) whether the GUI event handler (Button_Click) is synchronous or asynchronous.

Exception thrown in async method is not caught - why?


  1. As described for example in this blog post by Stephen Cleary - the state machine for async methods will capture exceptions from your code and place them on the returned task, i.e. method invocation will not throw, you will be able to catch exception if await the result.

  2. As for TaskScheduler.UnobservedTaskException - check out this answer and be sure to run code in Release mode.

Catching Exceptions in async methods when not called with await


exception was thrown before an await was done, that it would execute synchronously

Thought this is fairly true, but it doesn't mean you could catch the exception.

Because your code has async keyword, which turns the method into an async state machine i.e. encapsulated / wrapped by a special type. Any exception thrown from async state machine will get caught and re-thrown when the task is awaited (except for those async void ones) or they go unobserved, which can be caught in TaskScheduler.UnobservedTaskException event.

If you remove async keyword from the NonAwaitedMethod method, you can catch the exception.

A good way to observe this behavior is using this:

try
{
NonAwaitedMethod();

// You will still see this message in your console despite exception
// being thrown from the above method synchronously, because the method
// has been encapsulated into an async state machine by compiler.
Console.WriteLine("Method Called");
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}

So your code is compiled similarly to this:

try
{
var stateMachine = new AsyncStateMachine(() =>
{
try
{
NonAwaitedMethod();
}
catch (Exception ex)
{
stateMachine.Exception = ex;
}
});

// This does not throw exception
stateMachine.Run();
}
catch (Exception e)
{
Console.WriteLine("Exception Caught");
}

why does swapping from Task to void return type cause the exception to get caught

If the method returns a Task, the exception is caught by the task.

If the method is void, then the exception gets re-thrown from an arbitrary thread pool thread. Any unhandled exception thrown from thread pool thread will cause the app to crash, so chances are the debugger (or maybe the JIT debugger) is watching this sort of exceptions.

If you want to fire and forget but properly handle the exception, you could use ContinueWith to create a continuation for the task:

NonAwaitedMethod()
.ContinueWith(task => task.Exception, TaskContinuationOptions.OnlyOnFaulted);

Note you have to visit task.Exception property to make the exception observed, otherwise, task scheduler still will receive UnobservedTaskException event.

Or if the exception needs to be caught and processed in Main, the correct way to do that is using async Main methods.

Why does Exception from async void crash the app but from async Task is swallowed


TL;DR

This is because async void shouldn't be used! async void is only there to make legacy code work (e.g. event handlers in WindowsForms and WPF).

Technical details

This is because of how the C# compiler generates code for the async methods.

You should know that behind async/await there's a state machine (IAsyncStateMachine implementation) generated by the compiler.

When you declare an async method, a state machine struct will be generated for it. For your ex() method, this state machine code will look like:

void IAsyncStateMachine.MoveNext()
{
try
{
throw new Exception();
}
catch (Exception exception)
{
this.state = -2;
this.builder.SetException(exception);
}
}

Note that this.builder.SetException(exception); statement. For a Task-returning async method, this will be an AsyncTaskMethodBuilder object. For a void ex() method, it will be an AsyncVoidMethodBuilder.

The ex() method body will be replaced by the compiler with something like this:

private static Task ex()
{
ExAsyncStateMachine exasm;
exasm.builder = AsyncTaskMethodBuilder.Create();
exasm.state = -1;
exasm.builder.Start<ExAsyncStateMachine>(ref exasm);
return exasm.builder.Task;
}

(and for the async void ex(), there will be no last return line)

The method builder's Start<T> method will call the MoveNext method of the state machine. The state machine's method catches the exception in its catch block. This exception should normally be observed on the Task object - the AsyncTaskMethodBuilder.SetException method stores that exception object in the Task instance. When we drop that Task instance (no await), we don't see the exception at all, but the exception itself isn't thrown anymore.

In the state machine for async void ex(), there's an AsyncVoidMethodBuilder instead. Its SetException method looks different: since there's no Task where to store the exception, it has to be thrown. It happens in a different way, however, not just a normal throw:

AsyncMethodBuilderCore.ThrowAsync(exception, synchronizationContext);

The logic inside that AsyncMethodBuilderCore.ThrowAsync helper decides:

  • If there's a SynchronizationContext (e.g. we're on a UI thread of a WPF app), the exception will be posted on that context.
  • Otherwise, the exception will be queued on a ThreadPool thread.

In both cases, the exception won't be caught by a try-catch block that might be set up around the ex() call (unless you have a special SynchronizationContext that can do this, see e.g. Stephen Cleary's AsyncContext).

The reason is simple: when we post a throw action or enqueue it, we then simply return from the ex() method and thus leave the try-catch block. Then, the posted/enqueued action is executed (either on the same or on a different thread).

Catching an exception in an async method

First of all, your exception is handled. I believe you are seeing the code stop execution and display the error because you have Break When Thrown on for exceptions. Check your Exception Window (Debug -> Windows -> Exception Settings).

When you use a return type of void on an async method, you lack the ability to get any sort of information back from the method - it's fire and forget. Except in specific situations, this is bad practice. Always have your async methods return a Task or Task<T>:

private static async Task TestAsyncException()

Now, your main method can listen to the task:

static void Main(string[] args)
{
TestAsyncException().Wait(); // or whatever you want to do with the task
Console.Read();
}

Normally, you could use await to unwrap the task here, but that's not allowed in the application entry point.

Why I couldn't catch the exception in async function that has void return type?

Any time you have async void, you're basically breaking the ability to correctly signal completion and failure; the only way it can report failure is if the exception happens immediately and before any incomplete await - i.e. synchronously. In your case, the Task.Run guarantees that this is not synchronous, hence any knowledge of the outcome and failure: is lost.

Fundamentally, never write async void (unless you absolutely have to, for example in an event-handler). In addition to the problem above, it also has known complications with some SynchronizationContext implementations (in particular the legacy ASP.NET one), which means simply invoking an async void method is enough to crash your application (at least hypothetically; the sync-context caveat applies more to library authors than application authors, since library authors don't get to choose the application execution environment).

Remove the async void. If you want to return "nothing", then you should use async Task or async ValueTask as the signature:

static async Task MyMethodAsync() {
await TestThrowException();
}

(which could perhaps also be simplified to)

static Task MyMethodAsync()
=> TestThrowException();

and:

static async Task Main(string[] args) {
try {
await MyMethodAsync();
}
catch (Exception e) {
Console.WriteLine("Catch");
}
Console.ReadLine();
}

Why does Exception from async void crash the app but from async Task is swallowed


TL;DR

This is because async void shouldn't be used! async void is only there to make legacy code work (e.g. event handlers in WindowsForms and WPF).

Technical details

This is because of how the C# compiler generates code for the async methods.

You should know that behind async/await there's a state machine (IAsyncStateMachine implementation) generated by the compiler.

When you declare an async method, a state machine struct will be generated for it. For your ex() method, this state machine code will look like:

void IAsyncStateMachine.MoveNext()
{
try
{
throw new Exception();
}
catch (Exception exception)
{
this.state = -2;
this.builder.SetException(exception);
}
}

Note that this.builder.SetException(exception); statement. For a Task-returning async method, this will be an AsyncTaskMethodBuilder object. For a void ex() method, it will be an AsyncVoidMethodBuilder.

The ex() method body will be replaced by the compiler with something like this:

private static Task ex()
{
ExAsyncStateMachine exasm;
exasm.builder = AsyncTaskMethodBuilder.Create();
exasm.state = -1;
exasm.builder.Start<ExAsyncStateMachine>(ref exasm);
return exasm.builder.Task;
}

(and for the async void ex(), there will be no last return line)

The method builder's Start<T> method will call the MoveNext method of the state machine. The state machine's method catches the exception in its catch block. This exception should normally be observed on the Task object - the AsyncTaskMethodBuilder.SetException method stores that exception object in the Task instance. When we drop that Task instance (no await), we don't see the exception at all, but the exception itself isn't thrown anymore.

In the state machine for async void ex(), there's an AsyncVoidMethodBuilder instead. Its SetException method looks different: since there's no Task where to store the exception, it has to be thrown. It happens in a different way, however, not just a normal throw:

AsyncMethodBuilderCore.ThrowAsync(exception, synchronizationContext);

The logic inside that AsyncMethodBuilderCore.ThrowAsync helper decides:

  • If there's a SynchronizationContext (e.g. we're on a UI thread of a WPF app), the exception will be posted on that context.
  • Otherwise, the exception will be queued on a ThreadPool thread.

In both cases, the exception won't be caught by a try-catch block that might be set up around the ex() call (unless you have a special SynchronizationContext that can do this, see e.g. Stephen Cleary's AsyncContext).

The reason is simple: when we post a throw action or enqueue it, we then simply return from the ex() method and thus leave the try-catch block. Then, the posted/enqueued action is executed (either on the same or on a different thread).

How to handle the exception thrown by the async method with observable?

What's happening in your code is a direct consequence of you using Observable.Create and filling the observable with this code:

Enumerable.Range(1, 10).ToList().ForEach(x =>
{
observer.OnNext(x);
});

Observable.Create uses the current thread to create the observable, so the Enumerable.Range(1, 10).ToList().ForEach executes immediately on the current thread and the call to OnNext executes the handler(x).Wait() immediately.

You'll note, though, that the exception occurs in the delegate passed to the Subscribe. Internally there is code like this:

catch (Exception exception)
{
if (!autoDetachObserver.Fail(exception))
{
throw;
}
return autoDetachObserver;
}

That catches the exception in the subscribe, cancels the subscription - hence the "Observable Dispose" message - and then rethrows the exception and that's where your code catches it.

Now, if you wanted to do this properly in Rx, you'd avoid Observable.Create. It's a tempting way to create observables, but it leads to trouble.

Instead do this:

public async Task Test()
{
Func<int, Task> handler = async (i) =>
{
// simulate the handler logic
await Task.Delay(TimeSpan.FromSeconds(1));
// throw the exception to test
throw new Exception($"{i}");
};

await
Observable
.Range(1, 10)
.SelectMany(i => Observable.FromAsync(() => handler(i)))
.LastOrDefaultAsync();
}

But, of course, we want to handle the exception. The simple way is like this:

public async Task Test()
{
Func<int, Task> handler = async (i) =>
{
// simulate the handler logic
await Task.Delay(TimeSpan.FromSeconds(1));
// throw the exception to test
throw new Exception($"{i}");
};

await
Observable
.Range(1, 10)
.SelectMany(i =>
Observable
.FromAsync(() => handler(i))
.Catch<Unit, Exception>(ex =>
{
Console.WriteLine($"The exception is catch:{ex.ToString()}");
return Observable.Empty<Unit>();
}))
.LastOrDefaultAsync();
}

That now outputs the 10 exception errors and completes normally.



Related Topics



Leave a reply



Submit