377

Sometimes, under not reproducible circumstances, my WPF application crashes without any message. The application simply close instantly.

Where is the best place to implement the global Try/Catch block. At least I have to implement a messagebox with: "Sorry for the inconvenience ..."

Jakob Möllås
  • 4,239
  • 3
  • 33
  • 61
Scott Olson
  • 4,119
  • 3
  • 19
  • 13

9 Answers9

553

You can trap unhandled exceptions at different levels:

  1. AppDomain.CurrentDomain.UnhandledException From all threads in the AppDomain.
  2. Dispatcher.UnhandledException From a single specific UI dispatcher thread.
  3. Application.Current.DispatcherUnhandledException From the main UI dispatcher thread in your WPF application.
  4. TaskScheduler.UnobservedTaskException from within each AppDomain that uses a task scheduler for asynchronous operations.

You should consider what level you need to trap unhandled exceptions at.

Deciding between #2 and #3 depends upon whether you're using more than one WPF thread. This is quite an exotic situation and if you're unsure whether you are or not, then it's most likely that you're not.

heltonbiker
  • 26,657
  • 28
  • 137
  • 252
Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
  • 14
    Note for your item #3, I had to put .Current following Application like this: Application.Current.DispatcherUnhandledException += ... – Keith G Mar 16 '11 at 20:57
  • 1
    @Keith G -- all of these events are instance members, so you would need to hook them for each and every object required, depending upon your circumstances. – Drew Noakes Mar 17 '11 at 00:29
  • If the application just closes without going to any of these exception handlers it can also be that the System was shutdown with Environment.FailFast – Mo0gles Oct 18 '11 at 08:16
  • 27
    Also we must use **`AppDomain.CurrentDomain.UnhandledException`** for item #1. – Rev May 29 '12 at 04:21
  • 3
    If you are using Async tasks / TaskScheduler nowadays, I believe `TaskScheduler.UnobservedTaskException += ...` is orthogonal to these too. – DuckMaestro Sep 22 '12 at 23:23
  • 1
    In AppDomain.CurrentDomain.UnhandledException handler you have no way to prevent application from shutting down – Ilya Serbis Dec 02 '12 at 14:31
  • I bound the handler #3 through XAML. You find this event in the properties explorer of App.xaml. That adds `DispatcherUnhandledException='myHandler'` to the Application tag – OneWorld Jan 31 '13 at 09:50
  • 18
    Nice to see a compilation of the options with explanation of each. Here's how I'm currently approaching logging unhandled exceptions at the application level: https://gist.github.com/ronnieoverby/7568387 – Ronnie Overby Nov 20 '13 at 18:29
  • 1
    10 years later... I have inherited a WPF app w/ sub-optimal exception handling and was doing some research about what events I ought to handle and found this answer and my own advice. Thank you internet for augmenting my long-term memory. – Ronnie Overby Jan 11 '23 at 17:31
176

You can handle the AppDomain.UnhandledException event

EDIT: actually, this event is probably more adequate: Application.DispatcherUnhandledException

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
134

A quick example of code for Application.Dispatcher.UnhandledException:

public App() {
    this.Dispatcher.UnhandledException += OnDispatcherUnhandledException;
}

void OnDispatcherUnhandledException(object sender, System.Windows.Threading.DispatcherUnhandledExceptionEventArgs e) {
    string errorMessage = string.Format("An unhandled exception occurred: {0}", e.Exception.Message);
    MessageBox.Show(errorMessage, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
    // OR whatever you want like logging etc. MessageBox it's just example
    // for quick debugging etc.
    e.Handled = true;
}

I added this code in App.xaml.cs

Sergey
  • 1,837
  • 1
  • 18
  • 22
  • +1 for cut/paste code. If you're looking to spice up error message dialog, WPF extended toolkit has a [messagebox control](http://wpftoolkit.codeplex.com/wikipage?title=Extended%20WPF%20Toolkit%20Controls). – Arun M Mar 26 '11 at 14:48
  • 23
    Note that in certain situations, setting `e.Handled = true` can cause the application UI to close, while the process remains running on the machine silently. – qJake May 23 '14 at 14:42
  • WARNING! Using MessageBox.Show() in an unhandled top level exception can cause the process to hang! Log or do something else, but don't use use this API or display any UI. – Chris Bordeman Dec 24 '18 at 01:18
  • @qJake Can you please describe me better this situation please? – Francesco Bonizzi Jul 16 '20 at 12:48
  • 1
    An example of the process remaining after `e.Handled = true` is when an exception is raised in the constructor of the startup window. The window never shows, but because the exception is handled the app never crashes so the app remains running. I'm not yet sure the best way to handle this. `if (!(e is System.Windows.Markup.XamlParseException)) args.Handled = true;` seems to work but doesn't feel like the best solution. – br3nt Jan 18 '21 at 01:12
  • 1
    I think this would be a better check is `e.Handled = MainWindow?.IsLoaded ?? false;`. This would ensure `e.Handled` is only `true` is the MainWindow has loaded. – br3nt Jan 18 '21 at 01:36
45

I use the following code in my WPF apps to show a "Sorry for the inconvenience" dialog box whenever an unhandled exception occurs. It shows the exception message, and asks user whether they want to close the app or ignore the exception and continue (the latter case is convenient when a non-fatal exceptions occur and user can still normally continue to use the app).

In App.xaml add the Startup event handler:

<Application .... Startup="Application_Startup">

In App.xaml.cs code add Startup event handler function that will register the global application event handler:

using System.Windows.Threading;

private void Application_Startup(object sender, StartupEventArgs e)
{
    // Global exception handling  
    Application.Current.DispatcherUnhandledException += new DispatcherUnhandledExceptionEventHandler(AppDispatcherUnhandledException);    
}

void AppDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{    
    \#if DEBUG   // In debug mode do not custom-handle the exception, let Visual Studio handle it

    e.Handled = false;

    \#else

    ShowUnhandledException(e);    

    \#endif     
}

void ShowUnhandledException(DispatcherUnhandledExceptionEventArgs e)
{
    e.Handled = true;

    string errorMessage = string.Format("An application error occurred.\nPlease check whether your data is correct and repeat the action. If this error occurs again there seems to be a more serious malfunction in the application, and you better close it.\n\nError: {0}\n\nDo you want to continue?\n(if you click Yes you will continue with your work, if you click No the application will close)",

    e.Exception.Message + (e.Exception.InnerException != null ? "\n" + 
    e.Exception.InnerException.Message : null));

    if (MessageBox.Show(errorMessage, "Application Error", MessageBoxButton.YesNoCancel, MessageBoxImage.Error) == MessageBoxResult.No)   {
        if (MessageBox.Show("WARNING: The application will close. Any changes will not be saved!\nDo you really want to close it?", "Close the application!", MessageBoxButton.YesNoCancel, MessageBoxImage.Warning) == MessageBoxResult.Yes)
    {
        Application.Current.Shutdown();
    } 
}
Community
  • 1
  • 1
jurev
  • 1,457
  • 1
  • 14
  • 22
  • @Weston that link is dead – McKay Jul 29 '15 at 18:23
  • 2
    @McKay got deleted as a dupe of this: http://stackoverflow.com/questions/2004629/what-is-the-best-way-in-c-sharp-to-determine-whether-the-programmer-is-running-t – weston Jul 29 '15 at 18:26
  • @McKay No problem. FYI it sparked a meta question http://meta.stackoverflow.com/questions/300452/the-similar-questions-list-on-a-deleted-duplicate-questions-404-page-should-i – weston Jul 30 '15 at 14:02
  • Still kinda evil to just shut down without giving user a chance to save changes. What if it was a temporary network blip? – enorl76 Oct 21 '17 at 22:51
  • 1
    WARNING! Using MessageBox.Show() in an unhandled top level exception can cause the process to hang! Log or do something else, but don't use use this API or display any UI. – Chris Bordeman Dec 24 '18 at 01:19
20

Best answer is probably https://stackoverflow.com/a/1472562/601990.

Here is some code that shows how to use it:

App.xaml.cs

public sealed partial class App
{
    protected override void OnStartup(StartupEventArgs e)
    {
        // setting up the Dependency Injection container
        var resolver = ResolverFactory.Get();

        // getting the ILogger or ILog interface
        var logger = resolver.Resolve<ILogger>();
        RegisterGlobalExceptionHandling(logger);

        // Bootstrapping Dependency Injection 
        // injects ViewModel into MainWindow.xaml
        // remember to remove the StartupUri attribute in App.xaml
        var mainWindow = resolver.Resolve<Pages.MainWindow>();
        mainWindow.Show();
    }

    private void RegisterGlobalExceptionHandling(ILogger log)
    {
        // this is the line you really want 
        AppDomain.CurrentDomain.UnhandledException += 
            (sender, args) => CurrentDomainOnUnhandledException(args, log);

        // optional: hooking up some more handlers
        // remember that you need to hook up additional handlers when 
        // logging from other dispatchers, shedulers, or applications

        Application.Dispatcher.UnhandledException += 
            (sender, args) => DispatcherOnUnhandledException(args, log);

        Application.Current.DispatcherUnhandledException +=
            (sender, args) => CurrentOnDispatcherUnhandledException(args, log);

        TaskScheduler.UnobservedTaskException += 
            (sender, args) => TaskSchedulerOnUnobservedTaskException(args, log);
    }

    private static void TaskSchedulerOnUnobservedTaskException(UnobservedTaskExceptionEventArgs args, ILogger log)
    {
        log.Error(args.Exception, args.Exception.Message);
        args.SetObserved();
    }

    private static void CurrentOnDispatcherUnhandledException(DispatcherUnhandledExceptionEventArgs args, ILogger log)
    {
        log.Error(args.Exception, args.Exception.Message);
        // args.Handled = true;
    }

    private static void DispatcherOnUnhandledException(DispatcherUnhandledExceptionEventArgs args, ILogger log)
    {
        log.Error(args.Exception, args.Exception.Message);
        // args.Handled = true;
    }

    private static void CurrentDomainOnUnhandledException(UnhandledExceptionEventArgs args, ILogger log)
    {
        var exception = args.ExceptionObject as Exception;
        var terminatingMessage = args.IsTerminating ? " The application is terminating." : string.Empty;
        var exceptionMessage = exception?.Message ?? "An unmanaged exception occured.";
        var message = string.Concat(exceptionMessage, terminatingMessage);
        log.Error(exception, message);
    }
}
Community
  • 1
  • 1
MovGP0
  • 7,267
  • 3
  • 49
  • 42
  • It might be necessary to include `#if DEBUG` so Visual Studio handles exceptions like normal only when debugging. Awesome solution. –  Nov 01 '16 at 09:02
  • 4
    instead of `#if DEBUG` you should use the `[Conditional("DEBUG")]` attribute on `RegisterGlobalExceptionHandling` instead. This way you can ensure that the code compiles when changing the compiler target. – MovGP0 Dec 05 '16 at 16:28
  • 1
    besides, it is preferable to keep the logging of global exceptions also in production code. you can use the `ConditionalAttribute` for the configuration of the logger inside your dependency injection setup and just change the logging verbosity. – MovGP0 Dec 05 '16 at 16:33
11

In addition to the posts above:

Application.Current.DispatcherUnhandledException

will not catch exceptions that are thrown from a thread other than the main thread. You have to catch those exceptions on the same thread they are thrown. But if you want to Handle them on your global exception handler you can pass it to the main thread:

 System.Threading.Thread t = new System.Threading.Thread(() =>
    {
        try
        {
            ...
            //this exception will not be catched by 
            //Application.DispatcherUnhandledException
            throw new Exception("huh..");
            ...
        }
        catch (Exception ex)
        {
            //But we can handle it in the throwing thread
            //and pass it to the main thread wehre Application.
            //DispatcherUnhandledException can handle it
            System.Windows.Application.Current.Dispatcher.Invoke(
                System.Windows.Threading.DispatcherPriority.Normal,
                new Action<Exception>((exc) =>
                    {
                      throw new Exception("Exception from another Thread", exc);
                    }), ex);
        }
    });
philu
  • 795
  • 1
  • 8
  • 17
Tobias Hoefer
  • 1,058
  • 12
  • 29
  • This line `System.Windows.Threading.DispatcherPriority.Normal` made my day to finally trigger the exception on a given `DispatcherUnhandledException` event handler – Ole K Jan 20 '21 at 16:40
3

To supplement Thomas's answer, the Application class also has the DispatcherUnhandledException event that you can handle.

dustyburwell
  • 5,755
  • 2
  • 27
  • 34
3

A complete solution is here

it's explained very nice with sample code. However, be careful that it does not close the application.Add the line Application.Current.Shutdown(); to gracefully close the app.

karpanai
  • 235
  • 4
  • 14
2

As mentioned above

Application.Current.DispatcherUnhandledException will not catch exceptions that are thrown from another thread then the main thread.

That actual depend on how the thread was created

One case that is not handled by Application.Current.DispatcherUnhandledException is System.Windows.Forms.Timer for which Application.ThreadException can be used to handle these if you run Forms on other threads than the main thread you will need to set Application.ThreadException from each such thread

Jens
  • 2,327
  • 25
  • 34
  • copying from other thread at SO the answer by Hertzel Guinness: in the app.config will prevent your secondary threads exception from shutting down the application" – George Birbilis Nov 27 '15 at 00:09