Async method throws exception instantly but is swallowed when async keyword is removed

I'm getting some behaviour which I cannot understand when throwing exceptions in async methods.

The following code will throw an exception immediately when calling the ThrowNow method. If I comment that line out and throw the exception directly then the exception is swallowed and not raised in the Unobserved event handler.

public static async void ThrowNow(Exception ex){
    throw ex;
}

public static async Task TestExAsync()
{
    ThrowNow(new System.Exception("Testing")); // Throws exception immediately
    //throw new System.Exception("Testing");   // Exception is swallowed, not raised in unobserved event

    await Task.Delay(1000);
}

void Main()
{  
    var task = TestExAsync();
}

Something a little more confusing, if I remove the async keyword from the ThrowNow method, the exception is swallowed yet again.

I thought async methods run synchronously until reaching a blocking method. In this case, it seems that removing the async keyword is making it behave asynchronously.

Jon Skeet
people
quotationmark

I thought async methods run synchronously until reaching a blocking method.

They do, but they're still aware that they're executing within an asynchronous method.

If you throw an exception directly from an async void method, the async mechanism is aware that you'd have no way of observing that exception - it won't be thrown back to the caller, because exceptions thrown in async methods are only propagated through tasks. (The returned task becomes faulted.) It would be odd for an exception thrown before the first blocking await expression to be thrown directly, but exceptions afterwards to be handled differently.

As far as I'm aware, an exception thrown by an async void method is passed directly to the synchronization context, if there is one. (A continuation is posted to the synchronization context that just throws the exception.) In a simple console app, there isn't a synchronization context, so instead it's thrown as an unreported exception.

If you change your void method to return Task, then instead you'll just have an exception which could have been observed, but isn't (because you're not using the return value in TestExAsync).

Does that make any sense? Let me know if you'd like more clarification - it's all a bit tortuous (and I don't know how well documented it is).

EDIT: I've found a bit of documentation, in the C# 5 spec section 10.15.2:

If the return type of the async function is void, evaluation differs from the above in the following way: Because no task is returned, the function instead communicates completion and exceptions to the current thread's synchronization context. The exact definition of synchronization context is implementation-dependent, but is a representation of "where" the current thread is running. The synchronization context is notified when evaluation of a void-returning async function commences, completes successfully, or causes an uncaught exception to be thrown.

people

See more on this question at Stackoverflow