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 await
ed (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.