6

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

Jaycee
  • 3,098
  • 22
  • 31
  • potentially a duplicate of [Catching unhandled exception on separate threads](http://stackoverflow.com/questions/4284986/catching-unhandled-exception-on-separate-threads) – JMK Dec 12 '13 at 11:33
  • @JMK That does not adequately answer my question – Jaycee Dec 12 '13 at 11:54
  • Hence the use of the word 'potentially' – JMK Dec 12 '13 at 11:55
  • @JMK Was I wrong in assuming you voted to close my question? – Jaycee Dec 12 '13 at 11:57
  • I noticed you where using threads in your code, and thought it may be to do with exceptions being thrown in one thread not being caught in another which is commonly asked, hence the close vote. Nobody else has voted to close though so looks like I'm wrong – JMK Dec 12 '13 at 11:58
  • @probably won't get an answer now tho – Jaycee Dec 12 '13 at 12:03

2 Answers2

8

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 Tasks 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 awaited, so the exception is transferred to the Hello() Task. That in turn is awaited 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.

svick
  • 236,525
  • 50
  • 385
  • 514
  • 1
    Awesome answer :) I wish all answers on SO were as complete as this one. – Tim Dec 12 '13 at 20:15
  • @svick Thanks and +1 but could you please answer the question in my addendum? Assume there is no try/catch in button handler and async Task is used throughout. I am not observing any Task exception so would then expect TaskScheduler.UnobservedTaskException to run after some delay, but Application_ThreadException is called. Maybe I need a new question... – Jaycee Dec 13 '13 at 08:55
  • @Jaycee See update. You are observing *all* `Task` exceptions, either by using `await` or `Task.Run()`. – svick Dec 13 '13 at 09:47
0

I'm not sure if you solved your problem ? If the point was to catch your unhandeld exceptions, you could do this. Add to program.cs before Application.Run

Application.ThreadException += new ThreadExceptionEventHandler(Application_ThreadException);

Create 2 voids to handle the exception data.

static void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
{
  MessageBox.Show(e.Exception.Message, "Unhandled Thread Exception");
  // here you can log the exception ...
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
  MessageBox.Show((e.ExceptionObject as Exception).Message, "Unhandled UI Exception");
  // here you can log the exception ...
}
Simon Lausten
  • 37
  • 1
  • 1
  • 8
  • Hi, I did in fact solve the original issue to handle unhandled exceptions by ensuring the async methods used Task and not Void in their signatures all the way up the call stack. My outstanding issue was not knowing why TaskScheduler_UnobservedTaskException is not called instead of Application_ThreadException. – Jaycee Dec 12 '13 at 13:23