I've applied these handlers in a test application:
Application.ThreadException += Application_ThreadException;
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
I then create an exception on a nested Task/Async await on the form:
Despite the handler being fired - CurrentDomain.UnhandledException
- the app still crashes.
Why would it crash and not show the dialog and stay running?
System.Exception was unhandled Message: An unhandled exception of type 'System.Exception' occurred in mscorlib.dll Additional information: dd
private async void button1_Click(object sender, EventArgs e)
{
Console.WriteLine("Main " + Thread.CurrentThread.ManagedThreadId);
try
{
await Hello();
}
catch (Exception ex)
{
Console.WriteLine("Exception on main " + Thread.CurrentThread.ManagedThreadId);
}
}
private async static Task Hello() //changed from void
{
await Task.Run(() => new IGetRun().Run1());
}
internal class IGetRun
{
public async Task Run1() //changed from void
{
Console.WriteLine("Run1 " + Thread.CurrentThread.ManagedThreadId);
await Task.Run(() => new IGetRun2().Run2());
}
}
internal class IGetRun2
{
public void Run2()
{
Console.WriteLine("Run2 " + Thread.CurrentThread.ManagedThreadId);
throw new Exception("dd");
}
}
EDIT:
Looks like I forgot to declare each async method with Task and not void thus exception handling works predictably now. The only thing I still do not know is why - if I stop handling the exception in the button event - when the exception is caught Application_ThreadException
is fired and not TaskScheduler_UnobservedTaskException
Your original code does something that you should never do: calling async void
methods. And problematic exception handling is one reason for that.
Let's look at the event handlers one by one:
Application.ThreadException
only handles exceptions on the Winforms UI thread. The exception never got to the UI thread, so this event doesn't fire.TaskScheduler.UnobservedTaskException
only handles exceptions from Task
s that are unobserved (and even then, it does so when they are finalized, which might take a long time if you're not allocating much memory). But the unobserved exception is not tied to any Task
, so this doesn't fire either.AppDomain.CurrentDomain.UnhandledException
handles all exceptions that are still considered unobserved after the previous two event handlers. But it can't be used to set the exception as observed, so the application still terminates. This is the only handler that actually fires.Why exactly don't the first two handlers fire? In your code, you have Task.Run(() => new IGetRun().Run1())
, where Run1()
is an async void
method that throws an exception.
When there is an unobserved exception in an async void
method, the exception is rethrown on the current synchronization context. But since the method is running on a thread pool thread (because of the Task.Run()
), there is no synchronization context. So the exception is thrown on a thread pool thread, which is not observable by the first two event handlers.
As you already discovered, the fix for this issue is to use async Task
methods and await
them properly.
In your updated code, Run1()
is now an async Task
method. That Task
is unwrapped by Task.Run()
and then await
ed, so the exception is transferred to the Hello()
Task
. That in turn is await
ed from the async void button1_Click
handler on the UI thread.
This all means that there are no unobserved Task
exceptions and that the exception is rethrown on the UI synchronization context. In Winforms, that means Application.ThreadException
is raised, which is exactly what you're observing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With