WPF global exception handler [duplicate]
Asked Answered
B

9

379

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 ..."

Breakneck answered 24/9, 2009 at 15:40 Comment(2)
gotta love how the duplicate links back to this questionVoroshilov
This question has better answers.Quicklime
G
176

You can handle the AppDomain.UnhandledException event

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

Gang answered 24/9, 2009 at 15:43 Comment(6)
Add the handler in the forms constructor like this: AppDomain.Current.UnhandledException+=...Antigone
Bad idea if you create multiple instances of the window...Gang
Hi Thomas, thanks for your answer. Appdomain.UnHandledException works great for me.Breakneck
could add the handler at App.xaml.cs I guessLungi
Not working with MVVMIlysa
@FranzKiermaier the fact that you're using MVVM shouldn't change anything...Gang
M
555

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.

Mcwilliams answered 24/9, 2009 at 15:49 Comment(9)
Note for your item #3, I had to put .Current following Application like this: Application.Current.DispatcherUnhandledException += ...Chryselephantine
@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.Mcwilliams
If the application just closes without going to any of these exception handlers it can also be that the System was shutdown with Environment.FailFastBuryat
Also we must use AppDomain.CurrentDomain.UnhandledException for item #1.Instructions
If you are using Async tasks / TaskScheduler nowadays, I believe TaskScheduler.UnobservedTaskException += ... is orthogonal to these too.Mientao
In AppDomain.CurrentDomain.UnhandledException handler you have no way to prevent application from shutting downUnderwater
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 tagShockey
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: gist.github.com/ronnieoverby/7568387Pediatrics
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.Pediatrics
G
176

You can handle the AppDomain.UnhandledException event

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

Gang answered 24/9, 2009 at 15:43 Comment(6)
Add the handler in the forms constructor like this: AppDomain.Current.UnhandledException+=...Antigone
Bad idea if you create multiple instances of the window...Gang
Hi Thomas, thanks for your answer. Appdomain.UnHandledException works great for me.Breakneck
could add the handler at App.xaml.cs I guessLungi
Not working with MVVMIlysa
@FranzKiermaier the fact that you're using MVVM shouldn't change anything...Gang
T
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

Tout answered 23/3, 2011 at 6:13 Comment(6)
+1 for cut/paste code. If you're looking to spice up error message dialog, WPF extended toolkit has a messagebox control.Latton
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.Transposition
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.Honourable
@Transposition Can you please describe me better this situation please?Quartana
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.Marci
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.Marci
G
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();
    } 
}
Gatehouse answered 13/7, 2011 at 17:9 Comment(5)
@Weston that link is deadSystem
@System got deleted as a dupe of this: #2005129Fustanella
@System No problem. FYI it sparked a meta question meta.#300952Fustanella
Still kinda evil to just shut down without giving user a chance to save changes. What if it was a temporary network blip?Lugsail
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.Honourable
A
20

Best answer is probably https://mcmap.net/q/87022/-wpf-global-exception-handler-duplicate.

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);
    }
}
Aslant answered 27/10, 2016 at 22:46 Comment(3)
It might be necessary to include #if DEBUG so Visual Studio handles exceptions like normal only when debugging. Awesome solution.Hecate
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.Aslant
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.Aslant
G
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);
        }
    });
Gravitative answered 18/2, 2013 at 16:3 Comment(1)
This line System.Windows.Threading.DispatcherPriority.Normal made my day to finally trigger the exception on a given DispatcherUnhandledException event handlerSchwaben
V
3

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

Vaporous answered 24/9, 2009 at 15:52 Comment(0)
P
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.

Prittleprattle answered 8/12, 2014 at 10:19 Comment(0)
F
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

Fremd answered 11/9, 2014 at 11:16 Comment(1)
copying from other thread at SO the answer by Hertzel Guinness: <configuration> <runtime> <legacyUnhandledExceptionPolicy enabled="1"/> </runtime> </configuration> in the app.config will prevent your secondary threads exception from shutting down the application"Lungi

© 2022 - 2024 — McMap. All rights reserved.