web-dev-qa-db-fra.com

Gestionnaire d'exception global WPF

Parfois, dans des circonstances non reproductibles, mon application WPF se bloque sans aucun message. L'application se ferme simplement instantanément.

Où est le meilleur endroit pour implémenter le bloc global Try/Catch. Au moins, je dois implémenter une boîte de message avec: "Désolé pour le désagrément ..."

315
Scott Olson

Vous pouvez gérer l'événement AppDomain.UnhandledException

EDIT: en réalité, cet événement est probablement plus adéquat: Application.DispatcherUnhandledException

159
Thomas Levesque

Vous pouvez intercepter les exceptions non gérées à différents niveaux:

  1. AppDomain.CurrentDomain.UnhandledException De tous les threads de l'AppDomain.
  2. Dispatcher.UnhandledException À partir d'un seul thread de distributeur d'interface utilisateur spécifique.
  3. Application.Current.DispatcherUnhandledException À partir du fil de travail du dispatcher d'interface utilisateur principal dans votre application WPF.
  4. TaskScheduler.UnobservedTaskException à partir de chaque domaine AppDomain qui utilise un planificateur de tâches pour les opérations asynchrones.

Vous devez déterminer le niveau auquel vous devez capturer les exceptions non gérées.

Le choix entre # 2 et # 3 dépend de si vous utilisez plus d'un thread WPF. C'est une situation assez exotique et si vous ne savez pas si vous l'êtes ou non, il est fort probable que vous ne l'êtes pas.

485
Drew Noakes

Un exemple rapide de code pour 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;
}

J'ai ajouté ce code dans App.xaml.cs

107
Sergey

J'utilise le code suivant dans mes applications WPF pour afficher une boîte de dialogue "Désolé pour le dérangement" chaque fois qu'une exception non gérée se produit. Il affiche le message d'exception et demande à l'utilisateur s'il souhaite fermer l'application ou ignorer l'exception et continuer (ce dernier cas est pratique lorsqu'une exception non fatale se produit et que l'utilisateur peut toujours continuer à utiliser l'application).

Dans App.xaml, ajoutez le gestionnaire d'événements Startup:

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

Dans le code App.xaml.cs, ajoutez la fonction de gestionnaire d’événements de démarrage qui enregistrera le gestionnaire d’événements global d’application:

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();
    } 
}
41
jurev

La meilleure réponse est probablement https://stackoverflow.com/a/1472562/60199 .

Voici un code qui montre comment l'utiliser:

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);
    }
}
15
MovGP0

En plus des messages ci-dessus:

Application.Current.DispatcherUnhandledException

n'acceptera pas les exceptions émises par un autre thread que le thread principal. Vous devez gérer ces exceptions sur son thread actuel. Mais si vous voulez les gérer avec votre gestionnaire d'exception global, vous pouvez le transmettre au thread principal:

 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);
        }
    });
11
Tobias Hoefer

Pour compléter la réponse de Thomas, la classe Application possède également l'événement DispatcherUnhandledException que vous pouvez gérer.

3
dustyburwell

Une solution complète est ici

c'est très bien expliqué avec un exemple de code. Cependant, veillez à ne pas fermer l'application. Ajoutez la ligne Application.Current.Shutdown (); fermer gracieusement l'application.

3
karpanai

Comme mentionné ci-dessus

Application.Current.DispatcherUnhandledException n'acceptera pas les exceptions émises par un autre thread que le thread principal.

Cela dépend de la façon dont le fil a été créé

Un cas qui n'est pas traité par Application.Current.DispatcherUnhandledException est System.Windows.Forms.Timer pour lequel Application.ThreadException peut être utilisé pour les gérer si vous exécutez des formulaires sur des threads autres que le thread principal dont vous aurez besoin pour définir Application.ThreadException. de chacun de ces fils

2
Jens