Handling unhandled exceptions with async/await on Windows 8 and Windows Phone 8

Posted by: | Technical |

In Windows 8 and now Windows Phone 8 the async/await keywords give us a simpler, more concise way of managing asynchronous code.

But, what happens when stuff goes wrong unintentionally, unhandled exceptions occurring when using these constructs?

The short answer is of course it depends!

Let’s take the following async void method. If you authored and called this method in a Windows 8 app without doing anything else, it would crash the process, no ifs or buts, crash.

public async void Test()
{
    throw new Exception("boo");
}

You will read a lot of guidance on the web that says simple “don’t author async void methods!” for this very reason, but they are there for a purpose, fire and forget event handlers. Often these handlers are built into the lifecycle of a page’s execution so you are actually starting from within an async void to begin with.

Some solutions then dictate wrapping all of our code in a try/catch or in some sort of Invoke method which enables us to centralize the execution of code from async voids. Messy and error prone to say the least.

Let’s look at another scenario, not awaiting an async method call as follows where the method implementation of Go() calls the method Test() without awaiting the call.


public void Go()
{
    Test();
}

public async Task Test()
{
    throw new Exception("boo");
}

Now, you would get a compiler warning about this, the warning would urge you to await the call, but you might think “i don’t care , i don’t need to await it as it’s fire and forget so i’ll just carry on”. Might sound sane, but exceptions are simply swallowed, hidden and you will never know about them if you didn’t do anything else, not pretty.

So, we have 2 problems in summary:

  • unhandled exceptions within async void methods will crash the process.
  • unhandled exceptions within unawaited async calls will be swallowed and never surfaced.

The Solution

The solution is to ensure you can handle unhandled exceptions from these 2 additional scenarios in a global way alongside the normal unhandled exception handler for the application.

Essentially, we need to update our App constructor and add a couple of handlers.


public App()
{

    // set sync context for ui thread so async void exceptions can be handled, keeps process alive
    AsyncSynchronizationContext.Register();

    // ensure unobserved task exceptions (unawaited async methods returning Task or Task<T>) are handled
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

    // ensure general app exceptions are handled
    UnhandledException += App_UnhandledException;
}

void App_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
    e.Handled = true;
    ExceptionHandler.HandleException(new Exception(e.Message, e.Exception));
}

static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    e.SetObserved();
    ExceptionHandler.LogException(e.Exception);
}

The custom AsyncSynchronizationContext class essentially gets registered to allow you to hook into any unhandled exceptions occuring on async void calls made from the same originating thread (the UI thread in our instance).

Thanks to Mark Young for his work on this.


public class AsyncSynchronizationContext : SynchronizationContext
    {
        public static AsyncSynchronizationContext Register()
        {
            var syncContext = Current;
            if (syncContext == null)
                throw new InvalidOperationException("Ensure a synchronization context exists before calling this method.");

            var customSynchronizationContext = syncContext as AsyncSynchronizationContext;

            if (customSynchronizationContext == null)
            {
                customSynchronizationContext = new AsyncSynchronizationContext(syncContext);
                SetSynchronizationContext(customSynchronizationContext);
            }

            return customSynchronizationContext;
        }

        private readonly SynchronizationContext _syncContext;

        public AsyncSynchronizationContext(SynchronizationContext syncContext)
        {
            _syncContext = syncContext;
        }

        public override SynchronizationContext CreateCopy()
        {
            return new AsyncSynchronizationContext(_syncContext.CreateCopy());
        }

        public override void OperationCompleted()
        {
            _syncContext.OperationCompleted();
        }

        public override void OperationStarted()
        {
            _syncContext.OperationStarted();
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            _syncContext.Post(WrapCallback(d), state);
        }

        public override void Send(SendOrPostCallback d, object state)
        {
            _syncContext.Send(d, state);
        }

        private static SendOrPostCallback WrapCallback(SendOrPostCallback sendOrPostCallback)
        {
            return state =>
            {
                Exception exception = null;

                try
                {
                    sendOrPostCallback(state);
                }
                catch (Exception ex)
                {
                    exception = ex;
                }

                if (exception != null)
                    ExceptionHandler.HandleException(exception);
            };
        }
    }

In addition you can see we now have a global ExceptionHandler class who’s job it is to deal with and surface any unhandled exceptions where possible.

Note that the UnobservedTaskException event is not possible to surface on the UI, your only option here is to log. To avoid this ever firing, ensure you always await async calls as suggested above.

With Windows Phone 8 you can use a MessageBox or RadMessageBox (if you are using the excellent Telerik Windows Phone controls) to surface the message, code below for ExceptionHandler is for Windows 8 only.

The code sample below uses Caliburn.Micro for the easy Execute.OnUIThread marshalling call, but you can also make a call to CoreDispatcher.RunAsync but it’s a bit more verbose.


using Caliburn.Micro;

 public static class ExceptionHandler
    {

        public static void LogException(Exception ex)
        {
            // e.g. MarkedUp.AnalyticClient.Error(ex.Message, ex);
        }

        /// <summary>
        /// Handles failure for application exception on UI thread (or initiated from UI thread via async void handler)
        /// </summary>
        public static void HandleException(Exception ex)
        {

            LogException(ex);

            Execute.OnUIThread(async () =>
            {
                var dialog = new MessageDialog(GetDisplayMessage(ex), "Unknown Error");
                await dialog.ShowAsync();
            });

        }

        /// <summary>
        /// Gets the error message to display from an exception
        /// </summary>
        public static string GetDisplayMessage(Exception ex)
        {
            string errorMessage;
#if DEBUG
                errorMessage = (ex.Message + " " + ex.StackTrace);
#else
                errorMessage = "An unknown error has occurred, please try again";
#endif
        }

    }

Note that it’s entirely up to you what you do here, but the options are really to log and/or surface the exception information as your application requires it.

Logging can be done for example with markedup.com on Windows 8 and flurry.com on Windows Phone 8.

In summary, if you do nothing and liberally make use of async/awaits within you code you are likely to have an application that will either crash or will have problems which aren’t easily surfaced or understood.

I agree, these issues should be better taken care of by the framework and/or templates, but in the mean time we have to implement this sort of solution to improve the quality of our apps and get visibility on any problems.