21

This code throws an exception. Is it possible to define an application global handler that will catch it?

string x = await DoSomethingAsync();

Using .net 4.5 / WPF

noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 4
    What's wrong with try/catch around that statement? – Charles Prakash Dasari Mar 13 '14 at 04:55
  • 6
    Same thing that prompted Applicaton.DispatcherUnhandledException to be created. –  Mar 13 '14 at 05:05
  • Well that was created for exceptions that might occur which you cannot catch. In general you should catch exceptions that might arise in your code. However here are some links that might help you get along: http://stackoverflow.com/questions/14167746/appdomain-unhandledexception & http://msdn.microsoft.com/en-us/library/system.appdomain.unhandledexception%28v=vs.110%29.aspx – Charles Prakash Dasari Mar 13 '14 at 05:57
  • 1
    How is this code called? What have you already tried? – Stephen Cleary Mar 13 '14 at 12:26

5 Answers5

22

This is actually a good question, if I understood it correctly. I initially voted to close it, but now retracted my vote.

It is important to understand how an exception thrown inside an async Task method gets propagated outside it. The most important thing is that such exception needs to be observed by the code which handles the completion of the task.

For example, here is a simple WPF app, I'm on NET 4.5.1:

using System;
using System.Threading.Tasks;
using System.Windows;

namespace WpfApplication_22369179
{
    public partial class MainWindow : Window
    {
        Task _task;

        public MainWindow()
        {
            InitializeComponent();

            AppDomain.CurrentDomain.UnhandledException +=
                CurrentDomain_UnhandledException;
            TaskScheduler.UnobservedTaskException +=
                TaskScheduler_UnobservedTaskException;

            _task = DoAsync();
        }

        async Task DoAsync()
        {
            await Task.Delay(1000);

            MessageBox.Show("Before throwing...");

            GCAsync(); // fire-and-forget the GC

            throw new ApplicationException("Surprise");
        }

        async void GCAsync()
        {
            await Task.Delay(1000);

            MessageBox.Show("Before GC...");

            // garbage-collect the task without observing its exception 
            _task = null;
            GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        }

        void TaskScheduler_UnobservedTaskException(object sender,
            UnobservedTaskExceptionEventArgs e)
        {
            MessageBox.Show("TaskScheduler_UnobservedTaskException:" +
                e.Exception.Message);
        }

        void CurrentDomain_UnhandledException(object sender,
            UnhandledExceptionEventArgs e)
        {
            MessageBox.Show("CurrentDomain_UnhandledException:" +
                ((Exception)e.ExceptionObject).Message);
        }
    }
}

Once ApplicationException has been thrown, it goes unobserved. Neither TaskScheduler_UnobservedTaskException nor CurrentDomain_UnhandledException gets invoked. The exception remains dormant until the _task object gets waited or awaited. In the above example it never gets observed, so TaskScheduler_UnobservedTaskException will be invoked only when the task gets garbage-collected. Then this exception will be swallowed.

The old .NET 4.0 behavior, where the AppDomain.CurrentDomain.UnhandledException event gets fired and the app crashes, can be enabled by configuring ThrowUnobservedTaskExceptions in app.config:

<configuration>
    <runtime>
      <ThrowUnobservedTaskExceptions enabled="true"/>
    </runtime>
</configuration>

When enabled this way, AppDomain.CurrentDomain.UnhandledException will still be fired after TaskScheduler.UnobservedTaskException when the exception gets garbage-collected, rather than on the spot where it thrown.

This behavior is described by Stephen Toub in his "Task Exception Handling in .NET 4.5" blog post. The part about task garbage-collection is described in the comments to the post.

That's the case with async Task methods. The story is quite different for async void methods, which are typically used for event handlers. Let's change the code this way:

public MainWindow()
{
    InitializeComponent();

    AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
    TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

    this.Loaded += MainWindow_Loaded;
}

async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
    await Task.Delay(1000);

    MessageBox.Show("Before throwing...");

    throw new ApplicationException("Surprise");
}

Because it's async void there's no Task reference to hold on to (so there's nothing to be possibly observed or garbage-collected later). In this case, the exception is thrown immediately on the current synchronization context. For a WPF app, Dispatcher.UnhandledException will be fired first, then Application.Current.DispatcherUnhandledException, then AppDomain.CurrentDomain.UnhandledException. Finally, if none of these events are handled (EventArgs.Handled is not set to true), the app will crash, regardless of the ThrowUnobservedTaskExceptions setting. TaskScheduler.UnobservedTaskException is not getting fired in this case, for the same reason: there is no Task.

Ryan Lundy
  • 204,559
  • 37
  • 180
  • 211
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • 1
    Noseratio you are spot on in understanding my question. However The fact that UnobservedTaskException is not raised until the GC is run makes it functionally useless. Toub provides the IgnoreExceptions solution in the questions section of his post. I dont understand why the compiler does not add this when it creates the async method wrapper. Inside the continuation it should raise UnobservedTaskExpception if the event is handled. –  Mar 14 '14 at 18:15
  • 1
    @Sam, IMO this behavior makes perfect sense. A `Task` object is a promise, a delayed result. The fact that it has just completed doesn't mean it has to be observed right away. It may be observed in 10 minutes from now, as a result of composition with other tasks ( e.g. with `WhenAll`), or other specifics of your asynchronous logic. Why should the compiler make any assumptions about this and break such logic? It should not. – noseratio Mar 14 '14 at 22:06
  • You have complete control over this. Either handle the exception on the spot where it may occur with `try/catch`, or use something like the aforementioned `IgnoreException` to mark it as observed. – noseratio Mar 14 '14 at 22:07
  • 1
    Thank you Noseratio, what I am saying is that I need a way to create an "observer of last resort" or global handler. If I handle DispatcherUnhandledException I am not asking the compiler to make an assumption - I am giving it a specific instruction. If the compiler adds Toubs IgnoreExceptions or something similar it can fire DispatcherUnhandledException if it I handle it. –  Mar 14 '14 at 23:01
  • 1
    BTW look at the answer provided by Todd Menier. Although his answer is wrong, his reasoning is correct. Namely - I can define a global handler for a non-async application, why cant I define one for an async app? –  Mar 14 '14 at 23:03
  • @Sam, I disagree with that but I don't know how to explain it better than I already did. This behavior is dictated by the *asynchronous nature* of `Task`. As I said, it may get observed now, or it may get observed in 10 minutes from now, *still legitimately*. I don't want the compiler or `DispatcherUnhandledException` get in the way meanwhile. – noseratio Mar 14 '14 at 23:09
  • I've been playing around with this code and have discovered that the `AppDomain FirstChanceException` is always thrown in all cases where the task is not observed. http://pastebin.com/wfqP3XSh – Razor May 09 '14 at 11:15
  • @VincePanuccio, that's true and expected, but I don't think it's useful. This event will be fired for any simple case like this: `try { throw new Exception(); } catch(e) { /* handle */ }`, *before* the `catch` block has a chance to handle the error. – noseratio May 09 '14 at 11:18
  • @Noseratio In your question you asked for a global exception handler and this one indeed is global. So you want a global exception handler that fires only if the surrounding code is not handled by a try catch block or always fires after a catch block has dealt with it? :-) – Razor May 09 '14 at 11:23
  • @VincePanuccio, I'm not the author of the question, the OP is @Sam :) If you think `AppDomain.FirstChanceException` would suit him better, you should post your own answer here. – noseratio May 09 '14 at 11:26
  • @Noseratio Oops! My mistake :) – Razor May 09 '14 at 11:33
  • @Noseratio If I throw an exception after `GC.Collect` then the UnhandlledException event is fired even though the Task no longer exists. The fire-and-forget method `GCAsync()` is not being observed. Can you explain this behavior ? – Razor May 09 '14 at 12:04
  • @VincePanuccio, I need to see and try the actual code. Can you post it as a separate question? – noseratio May 09 '14 at 13:09
1

EDITED as per @Noseration's comment

In .NET 4.5 in async code you can handle unobserved exceptions by registering a handler for the TaskScheduler.UnobservedTaskException event. An exception is deemed unobserved if you do not access the Task.Result, Task.Exception properties and you do not call Task.Wait.

After the unobserved exception reaches the TaskScheduler.UnobservedTaskException event handler, the default behaviour is to swallow this exception so the program does not crash. This behaviour can be changed in the configuration file by adding the following:

<configuration> 
   <runtime> 
      <ThrowUnobservedTaskExceptions enabled="true"/> 
   </runtime> 
</configuration>
NeddySpaghetti
  • 13,187
  • 5
  • 32
  • 61
  • True, but in this case he is awaiting the result so the task will be considered observed. – Todd Menier Mar 14 '14 at 02:29
  • This **was** true for .NET 4.0, but not anymore for .NET 4.5+. Check [my answer](http://stackoverflow.com/a/22395161/1768303) for details. – noseratio Mar 14 '14 at 03:38
1

Binding an event to the AppDomain.CurrentDomain.FirstChanceException will guarantee you that your exception will be caught. As @Noseratio pointed out, you'll be notified of every exception in your application, even if the exception is handled gracefully within a catch block and the application continues on.

However, I still see this event being useful for at least capturing the last few exceptions thrown before an application halted or perhaps some other debugging scenario.

If you want to protect yourself against this

string x = await DoSomethingAsync();

My advice to you is, don't do that, add a try catch block :-)

Razor
  • 17,271
  • 25
  • 91
  • 138
1

You can always do the following to handle the exception using Application.DispatcherUnhandledException method. Of course it would be given to you inside a TargetInvocationException and might not be as pretty as other methods. But it works perfectly fine

_executeTask = executeMethod(parameter);
_executeTask.ContinueWith(x =>
{
    Dispatcher.CurrentDispatcher.Invoke(new Action<Task>((task) =>
    {
        if (task.Exception != null)
           throw task.Exception.Flatten().InnerException;
    }), x);
}, TaskContinuationOptions.OnlyOnFaulted);
Hossein Shahdoost
  • 1,692
  • 18
  • 32
0

Well, how would you define an application global handler to deal with an exception in this case?

string x = DoSomething();

Chances are the answer to your question is exactly the same. It appears you are properly awaiting an async method, and the compiler goes to great lengths to ensure that any exception that occurs in the async method is propagated and unwound in a way that allows you to handle it just like you would in synchronous code. This is one of the primary benefits of async/await.

Todd Menier
  • 37,557
  • 17
  • 150
  • 173