Attraper globalement des exceptions dans une application WPF?

242

Nous avons une application WPF dont certaines parties peuvent lever des exceptions lors de l'exécution. J'aimerais intercepter globalement toute exception non gérée et les enregistrer, mais sinon, continuez l'exécution du programme comme si rien ne s'était passé (un peu comme les VB On Error Resume Next).

Est-ce possible en C #? Et si oui, où aurais-je exactement besoin de mettre le code de gestion des exceptions?

Actuellement, je ne vois aucun point unique où je pourrais boucler un try/ catchautour et qui intercepterait toutes les exceptions qui pourraient se produire. Et même alors, j'aurais laissé tout ce qui a été exécuté à cause de la capture. Ou est-ce que je pense ici dans des directions horriblement erronées?

ETA: Parce que de nombreuses personnes ci-dessous l'ont souligné: L'application n'est pas destinée au contrôle des centrales nucléaires. S'il se bloque, ce n'est pas très grave, mais des exceptions aléatoires qui sont principalement liées à l'interface utilisateur sont une nuisance dans le contexte où il serait utilisé. Il y en avait (et il y en a probablement encore) et comme il utilise une architecture de plug-in et peut être étendu par d'autres (également des étudiants dans ce cas; donc pas de développeurs expérimentés capables d'écrire du code sans erreur).

Quant aux exceptions qui sont interceptées: je les enregistre dans un fichier journal, y compris la trace de pile complète. C'était tout l'intérêt de cet exercice. Juste pour contrer ces gens qui prenaient mon analogie avec OERN de VB trop littéralement.

Je sais qu'ignorer aveuglément certaines classes d'erreurs est dangereux et peut corrompre mon instance d'application. Comme dit précédemment, ce programme n'est essentiel à la mission de personne. Personne sain d'esprit ne parierait la survie de la civilisation humaine dessus. C'est simplement un petit outil pour tester certaines approches de conception par rapport à. génie logiciel.

Pour l'utilisation immédiate de l'application, il n'y a pas beaucoup de choses qui peuvent se produire sur une exception:

  • Aucune gestion des exceptions - boîte de dialogue d'erreur et sortie de l'application. L'expérience doit être répétée, mais probablement avec un autre sujet. Aucune erreur n'a été enregistrée, ce qui est regrettable.
  • Gestion des exceptions générique - erreur bénigne piégée, aucun dommage causé. Cela devrait être le cas commun à en juger par toutes les erreurs que nous avons vues pendant le développement. Ignorer ce type d'erreurs ne devrait pas avoir de conséquences immédiates; les structures de données de base sont suffisamment testées pour pouvoir y survivre facilement.
  • Gestion des exceptions générique - erreur grave interceptée, peut-être planter ultérieurement. Cela peut arriver rarement. Nous ne l'avons jamais vu jusqu'à présent. L'erreur est enregistrée de toute façon et un plantage peut être inévitable. C'est donc conceptuellement similaire au tout premier cas. Sauf que nous avons une trace de pile. Et dans la majorité des cas, l'utilisateur ne le remarquera même pas.

En ce qui concerne les données d'expérience générées par le programme: Une erreur grave entraînerait au pire le fait qu'aucune donnée ne soit enregistrée. Des changements subtils qui modifient légèrement le résultat de l'expérience sont très peu probables. Et même dans ce cas, si les résultats semblent douteux, l'erreur a été enregistrée; on peut encore jeter ce point de données s'il s'agit d'une valeur aberrante totale.

Pour résumer: Oui, je me considère toujours au moins partiellement sain d'esprit et je ne considère pas une routine globale de gestion des exceptions qui laisse le programme en cours d'exécution être nécessairement totalement mauvais. Comme indiqué deux fois auparavant, une telle décision peut être valable, selon l'application. Dans ce cas, il a été jugé une décision valide et non une connerie totale et totale. Pour toute autre application, cette décision peut sembler différente. Mais s'il vous plaît, n'accusez pas moi ou les autres personnes qui ont travaillé sur ce projet pour potentiellement faire exploser le monde simplement parce que nous ignorons les erreurs.

Note latérale: Il y a exactement un utilisateur pour cette application. Ce n'est pas quelque chose comme Windows ou Office qui est utilisé par des millions de personnes où le coût de la bulle d'exceptions pour l'utilisateur serait déjà très différent en premier lieu.

Joey
la source
Cela ne fait pas vraiment penser - oui, vous voulez probablement quitter l'application. MAIS, ce ne serait pas bien de commencer par enregistrer l'exception avec son StackTrace? Si tout ce que vous avez obtenu de l'utilisateur était "Votre application s'est bloquée lorsque j'ai appuyé sur ce bouton", vous ne pourrez peut-être jamais résoudre le problème car vous ne disposiez pas d'informations suffisantes. Mais si vous avez d'abord enregistré l'exception avant d'interrompre l'application de manière plus agréable, vous disposerez de beaucoup plus d'informations.
Russ
J'ai développé ce point un peu dans la question maintenant. Je connais les risques encourus et pour cette application particulière, elle a été jugée acceptable. Et abandonner l'application pour quelque chose d'aussi simple qu'un index hors limites pendant que l'interface utilisateur essayait de faire une belle animation est exagéré et inutile. Oui, je ne connais pas la cause exacte, mais nous avons des données pour étayer l'affirmation selon laquelle la grande majorité des cas d'erreur est bénigne. Les problèmes graves que nous masquons peuvent entraîner le blocage de l'application, mais c'est ce qui se serait produit sans la gestion des exceptions globales de toute façon.
Joey
Autre remarque: si vous évitez les plantages avec cette approche, les utilisateurs l'adoreront très probablement.
lahjaton_j
Voir l'exemple Windows Gérer les exceptions non gérées dans WPF (la collection la plus complète de gestionnaires) en C # pour Visual Studio 2010 . Il contient 5 exemples, notamment AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException et AppDomain.CurrentDomain.UnhandledException.
user34660
Je voudrais ajouter que le flux de code de type VB On Error Resume Nextn'est pas possible en C #. Après un Exception(C # n'a pas "d'erreurs"), vous ne pouvez pas simplement reprendre avec l'instruction suivante: l'exécution se poursuivra dans un catchbloc - ou dans l'un des gestionnaires d'événements décrits dans les réponses ci-dessous.
Mike

Réponses:

191

Utilisez le Application.DispatcherUnhandledException Event. Voir cette question pour un résumé (voir la réponse de Drew Noakes ).

Sachez qu'il y aura toujours des exceptions qui empêcheront une reprise réussie de votre application, comme après un débordement de pile, une mémoire épuisée ou une perte de connectivité réseau pendant que vous essayez d'enregistrer dans la base de données.

David Schmitt
la source
Merci. Et oui, je suis conscient qu'il y a des exceptions dont je ne peux pas récupérer, mais la grande majorité de celles qui pourraient arriver sont bénignes dans ce cas.
Joey
14
Si votre exception se produit sur un thread d'arrière-plan (par exemple, en utilisant ThreadPool.QueueUserWorkItem), cela ne fonctionnera pas.
Szymon Rozga
@siz: bon point, mais ceux-ci peuvent être traités avec un bloc d'essai normal dans le workitem, non?
David Schmitt
1
@PitiOngmongkolkul: Le gestionnaire est appelé comme événement à partir de votre boucle principale. Lorsque les gestionnaires d'événements reviennent, votre application continue normalement.
David Schmitt
4
Il semble que nous devons définir e.Handled = true, où e est un DispatcherUnhandledExceptionEventArgs, pour ignorer le gestionnaire par défaut qui quitte les programmes. msdn.microsoft.com/en-us/library/…
Piti Ongmongkolkul
56

Exemple de code utilisant NLog qui interceptera les exceptions levées de tous les threads de l'AppDomain , du thread du répartiteur d'interface utilisateur et des fonctions asynchrones :

App.xaml.cs:

public partial class App : Application
{
    private static Logger _logger = LogManager.GetCurrentClassLogger();

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        SetupExceptionHandling();
    }

    private void SetupExceptionHandling()
    {
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
            LogUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");

        DispatcherUnhandledException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
            e.Handled = true;
        };

        TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
            e.SetObserved();
        };
    }

    private void LogUnhandledException(Exception exception, string source)
    {
        string message = $"Unhandled exception ({source})";
        try
        {
            System.Reflection.AssemblyName assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();
            message = string.Format("Unhandled exception in {0} v{1}", assemblyName.Name, assemblyName.Version);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Exception in LogUnhandledException");
        }
        finally
        {
            _logger.Error(exception, message);
        }
    }
Hüseyin Yağlı
la source
10
C'est la réponse la plus complète ici! Les exceptions du planificateur Taks sont incluses. Meilleur pour moi, code propre et simple.
Nikola Jovic
3
Récemment, j'ai eu une corruption App.config chez un client, et son application ne démarrait même pas parce que NLog essayait de lire depuis App.config et a levé une exception. Étant donné que cette exception se trouvait dans l'initialiseur de l'enregistreur statique , elle n'était pas interceptée par le UnhandledExceptiongestionnaire. J'ai dû regarder la visionneuse du journal des événements de Windows pour trouver ce qui se passait ...
heltonbiker
Je recommande de définir e.Handled = true;à UnhandledExceptionque l'application ne se bloquera pas sur les exceptions d'interface utilisateur
Apfelkuacha
30

AppDomain.UnhandledException, événement

Cet événement fournit une notification des exceptions non interceptées. Il permet à l'application de consigner des informations sur l'exception avant que le gestionnaire par défaut du système ne signale l'exception à l'utilisateur et ne ferme l'application.

   public App()
   {
      AppDomain currentDomain = AppDomain.CurrentDomain;
      currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);    
   }

   static void MyHandler(object sender, UnhandledExceptionEventArgs args) 
   {
      Exception e = (Exception) args.ExceptionObject;
      Console.WriteLine("MyHandler caught : " + e.Message);
      Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
   }

Si l'événement UnhandledException est géré dans le domaine d'application par défaut, il y est déclenché pour toute exception non gérée dans n'importe quel thread, quel que soit le domaine d'application dans lequel le thread a démarré. Si le thread a démarré dans un domaine d'application doté d'un gestionnaire d'événements pour UnhandledException, l'événement est déclenché dans ce domaine d'application. Si ce domaine d'application n'est pas le domaine d'application par défaut et qu'il existe également un gestionnaire d'événements dans le domaine d'application par défaut, l'événement est déclenché dans les deux domaines d'application.

Par exemple, supposons qu'un thread démarre dans le domaine d'application "AD1", appelle une méthode dans le domaine d'application "AD2" et à partir de là appelle une méthode dans le domaine d'application "AD3", où il lève une exception. Le premier domaine d'application dans lequel l'événement UnhandledException peut être déclenché est "AD1". Si ce domaine d'application n'est pas le domaine d'application par défaut, l'événement peut également être déclenché dans le domaine d'application par défaut.

CharithJ
la source
copie à partir de l'URL que vous avez pointée (qui ne parle que des applications console et des applications WinForms): "À partir du .NET Framework 4, cet événement n'est pas déclenché pour les exceptions qui corrompent l'état du processus, telles que les débordements de pile ou les violations d'accès, sauf si le gestionnaire d'événements est critique pour la sécurité et possède l'attribut HandleProcessCorruptedStateExceptionsAttribute. "
George Birbilis
1
@GeorgeBirbilis: vous pouvez vous abonner à l'événement UnhandledException dans le constructeur de l'application.
CharithJ
18

En plus de ce que d'autres ont mentionné ici, notez que la combinaison Application.DispatcherUnhandledException(et ses similitudes ) avec

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

dans le app.configvous empêchera votre exception de threads secondaires de fermer l'application.

Hertzel Guinness
la source
2

Voici un exemple complet utilisant NLog

using NLog;
using System;
using System.Windows;

namespace MyApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public App()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = (Exception)e.ExceptionObject;
            logger.Error("UnhandledException caught : " + ex.Message);
            logger.Error("UnhandledException StackTrace : " + ex.StackTrace);
            logger.Fatal("Runtime terminating: {0}", e.IsTerminating);
        }        
    }


}
Développeur
la source
-4

Comme "VB's On Error Resume Next?" Cela semble un peu effrayant. La première recommandation est de ne pas le faire. La deuxième recommandation est de ne pas le faire et de ne pas y penser. Vous devez mieux isoler vos défauts. Quant à la façon d'aborder ce problème, cela dépend de la façon dont votre code est structuré. Si vous utilisez un modèle comme MVC ou similaire, cela ne devrait pas être trop difficile et ne nécessiterait certainement pas un avaleur d'exceptions global. Deuxièmement, recherchez une bonne bibliothèque de journalisation comme log4net ou utilisez le traçage. Nous aurions besoin de plus de détails, tels que les types d'exceptions dont vous parlez et les parties de votre application susceptibles d'entraîner la levée d'exceptions.

BobbyShaftoe
la source
2
Le fait est que je souhaite que l'application continue de fonctionner même après des exceptions non détectées. Je veux juste les enregistrer (nous avons un cadre de journalisation personnalisé pour cela, basé sur nos besoins) et je n'ai pas besoin d'interrompre tout le programme simplement parce qu'un plugin a fait quelque chose de bizarre dans son code d'interaction utilisateur. D'après ce que j'ai vu jusqu'à présent, il n'est pas trivial d'isoler proprement les causes car les différentes parties ne se connaissent pas vraiment.
Joey
9
Je comprends, mais vous devez être très prudent de continuer après une exception que vous n'examinez pas, il y a la possibilité de corruption de données et ainsi de suite à considérer.
BobbyShaftoe
3
Je suis d'accord avec BobbyShaftoe. Ce n'est pas la bonne façon de procéder. Laissez l'application se bloquer. Si l'erreur est sur un plugin, corrigez ou demandez à quelqu'un de réparer le plugin. Le laisser fonctionner est EXTRÊMEMENT dangereux. Vous obtiendrez des effets secondaires étranges et atteindrez un point où vous ne pourrez pas trouver d'explication logique aux bogues qui se produisent dans votre application.
1
J'ai développé ce point un peu dans la question maintenant. Je connais les risques encourus et pour cette application particulière, elle a été jugée acceptable. Et abandonner l'application pour quelque chose d'aussi simple qu'un index hors limites pendant que l'interface utilisateur essayait de faire une belle animation est exagéré et inutile. Oui, je ne connais pas la cause exacte, mais nous avons des données pour étayer l'affirmation selon laquelle la grande majorité des cas d'erreur est bénigne. Les problèmes graves que nous masquons peuvent entraîner le blocage de l'application, mais c'est ce qui se serait produit sans la gestion des exceptions globales de toute façon.
Joey
Cela devrait être un commentaire, pas une réponse. Répondre à "comment faire" avec "vous ne devriez probablement pas" n'est pas une réponse, c'est un commentaire.
Josh Noe