Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unhandled exception handler not called for Metro / WinRT UI async void event handler

Consider the following to be extracts from a Windows 8 Metro / WinRT app, which have been reduced to the bare minimum required to show the anomaly:

public class App : Application
{
    public App()
    {
        UnhandledException += (sender, e) => e.Handled = true;
    }
}

public class MainPage : Page
{
    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }

    private async void Button_Click_2(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }
}

So given a Metro UI with two buttons and their click event handlers, the only difference is that the second event handler is marked as async.

Then clicking on each button, I would expect the UnhandledException handler to be called in both cases, since they (should) both be entered via the UI thread and associated synchronization context. My understanding is that, for async void methods, any exceptions should be captured and 'rethrown' (preserving the original stacktrace) via the initial synchronization context, which is also clearly stated in the Async / Await FAQ.

But the UnhandledException handler is not called in the async case, so the application crashes! Since this challenges what I consider an otherwise very intuitive model, I need to know why! Yes, I know I could wrap the body of the handler in a try { } catch { }, but my question is why isn't the backstop UnhandledException handler called?

To further emphasise why this doesn't make sense, consider the following practically identical extracts from a WPF app also using async / await and targeting .NET Framework 4.5:

public class App : Application
{
    public App()
    {
        DispatcherUnhandledException += (sender, e) => e.Handled = true;
    }
}

public class MainWindow : Window
{
    private void Button_Click_1(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }

    private async void Button_Click_2(object sender, RoutedEventArgs e)
    {
        throw new NotSupportedException();
    }
}

[There is a subtle difference that WPF has both an Application DispatcherUnhandledException event handler as well as an AppDomain UnhandledException event handler, but you can only mark the exception as 'handled' in the DispatcherUnhandledException, which aligns with the Metro / WinRT Application UnhandledException event handler above.]

Then clicking on each button, the DispatcherUnhandledException handler is indeed called in both cases, as expected, and the application does not crash.

like image 830
Sean Fausett Avatar asked Sep 10 '12 01:09

Sean Fausett


1 Answers

As explained in the documentation (source: http://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.application.unhandledexception.aspx):

It’s important to be aware of several limitations of the Application.UnhandledException event. This event is only used with exceptions encountered by the XAML framework. Exceptions encountered by other Windows Runtime components or parts of the application that are not connected to the XAML framework will not result in this event being raised.

For example, if a different Windows component calls into application code and an exception is thrown and not caught, the UnhandledException event won’t be raised. If the application creates a worker thread, and then raises an exception on the worker thread, the UnhandledException event won’t be raised.

As pointed out in this conversation, only way to retrieve exceptions happening in a worker thread is to wrap them in a try/catch block. As a consequence, here's the workaround I'm using in my app: instead of using Task.Run or equivalents to execute code on a worker thread from the UI, I'm using this method:

/// <summary>
/// Runs code in a worker thread and retrieves a related exception if any.
/// </summary>
/// <param name="target">The target.</param>
/// <param name="action">The action.</param>
public static void SafeRun(this DependencyObject target, Action action)
{
    Task task = ThreadPool.RunAsync(o =>
    {
        try
        {
            action();
        }
        catch (Exception ex)
        {
            /* Call the same error logging logic as in the UnhandledException event handler here. */
        }
    }).AsTask();
    task.Wait();
}

Bottom line is that to log all errors in your app, you should:

  1. Subscribre to the UnhandledException event to get XAML stack related errors,
  2. Wrap actions happening in a worker threads in a try/catch block.
like image 176
canderso Avatar answered Oct 29 '22 11:10

canderso