Ausnahmen in einer WPF-Anwendung global abfangen?

242

Wir haben eine WPF-Anwendung, bei der Teile davon zur Laufzeit Ausnahmen auslösen können. Ich möchte alle nicht behandelten Ausnahmen global abfangen und protokollieren, aber ansonsten die Programmausführung fortsetzen, als wäre nichts passiert (ein bisschen wie bei VBs On Error Resume Next).

Ist das in C # möglich? Und wenn ja, wo genau müsste ich den Code für die Ausnahmebehandlung eingeben?

Derzeit sehe ich keinen einzigen Punkt, an dem ich ein try/ catchumschließen könnte und der alle möglichen Ausnahmen abfangen würde. Und selbst dann hätte ich alles verlassen, was wegen des Fangs hingerichtet wurde. Oder denke ich hier in schrecklich falsche Richtungen?

ETA: Weil viele Leute unten darauf hingewiesen haben: Die Anwendung ist nicht für die Steuerung von Kernkraftwerken. Wenn es abstürzt, ist es keine große Sache, aber zufällige Ausnahmen, die hauptsächlich mit der Benutzeroberfläche zusammenhängen, sind in dem Kontext, in dem es verwendet wird, ein Ärgernis. Es gab (und gibt es wahrscheinlich immer noch) einige davon und da es eine Plugin-Architektur verwendet und möglicherweise von anderen erweitert wird (in diesem Fall auch von Studenten; also keine erfahrenen Entwickler, die in der Lage sind, vollständig fehlerfreien Code zu schreiben).

Was die Ausnahmen betrifft, die abgefangen werden: Ich protokolliere sie in einer Protokolldatei, einschließlich der vollständigen Stapelverfolgung. Das war der springende Punkt dieser Übung. Nur um den Leuten entgegenzuwirken, die meine Analogie zu VBs OERN zu wörtlich genommen haben.

Ich weiß, dass das blinde Ignorieren bestimmter Fehlerklassen gefährlich ist und meine Anwendungsinstanz beschädigen kann. Wie bereits erwähnt, ist dieses Programm für niemanden geschäftskritisch. Niemand, der bei klarem Verstand ist, würde das Überleben der menschlichen Zivilisation darauf wetten. Es ist einfach ein kleines Werkzeug zum Testen bestimmter Designansätze. Softwareentwicklung.

Für die sofortige Verwendung der Anwendung können in einer Ausnahme nicht viele Dinge passieren:

  • Keine Ausnahmebehandlung - Fehlerdialog und Anwendungs-Exit. Das Experiment muss wiederholt werden, wenn auch wahrscheinlich mit einem anderen Probanden. Es wurden keine Fehler protokolliert, was bedauerlich ist.
  • Allgemeine Ausnahmebehandlung - harmloser Fehler abgefangen, kein Schaden angerichtet. Dies sollte der übliche Fall sein, der anhand aller Fehler beurteilt wird, die wir während der Entwicklung gesehen haben. Das Ignorieren dieser Art von Fehlern sollte keine unmittelbaren Konsequenzen haben. Die Kerndatenstrukturen sind so gut getestet, dass sie dies leicht überleben können.
  • Allgemeine Ausnahmebehandlung - schwerwiegender Fehler, möglicherweise Absturz zu einem späteren Zeitpunkt. Dies kann selten vorkommen. Wir haben es bisher noch nie gesehen. Der Fehler wird trotzdem protokolliert und ein Absturz kann unvermeidlich sein. Dies ist also konzeptionell dem allerersten Fall ähnlich. Nur dass wir einen Stack-Trace haben. Und in den meisten Fällen wird der Benutzer es nicht einmal bemerken.

Zu den vom Programm generierten Versuchsdaten: Ein schwerwiegender Fehler würde im schlimmsten Fall nur dazu führen, dass keine Daten aufgezeichnet werden. Subtile Änderungen, die das Ergebnis des Experiments geringfügig verändern, sind ziemlich unwahrscheinlich. Und selbst in diesem Fall wurde der Fehler protokolliert, wenn die Ergebnisse zweifelhaft erscheinen. man kann diesen Datenpunkt immer noch wegwerfen, wenn es ein totaler Ausreißer ist.

Zusammenfassend: Ja, ich betrachte mich immer noch als zumindest teilweise gesund und ich betrachte keine globale Ausnahmebehandlungsroutine, die das Programm laufen lässt, als absolut böse. Wie bereits zweimal erwähnt, kann eine solche Entscheidung je nach Antrag gültig sein. In diesem Fall wurde es als eine gültige Entscheidung beurteilt und nicht als totaler und völliger Schwachsinn. Für jede andere Anwendung kann diese Entscheidung anders aussehen. Aber bitte beschuldigen Sie mich oder die anderen Leute, die an diesem Projekt gearbeitet haben, nicht, die Welt in die Luft zu jagen, nur weil wir Fehler ignorieren.

Randnotiz: Es gibt genau einen Benutzer für diese Anwendung. Es ist nicht so etwas wie Windows oder Office, das von Millionen verwendet wird, bei denen die Kosten für Ausnahmen für den Benutzer bereits sehr unterschiedlich wären.

Joey
quelle
Das denkt nicht wirklich nach - ja, Sie möchten wahrscheinlich, dass die Anwendung beendet wird. ABER, wäre es nicht schön, die Ausnahme zuerst mit ihrem StackTrace zu protokollieren? Wenn Sie vom Benutzer nur "Ihre Anwendung ist abgestürzt, als ich diese Schaltfläche gedrückt habe" erhalten haben, können Sie das Problem möglicherweise nie beheben, da Sie nicht über ausreichende Informationen verfügen. Wenn Sie die Ausnahme jedoch zuerst protokolliert haben, bevor Sie die Anwendung angenehmer abbrechen, erhalten Sie erheblich mehr Informationen.
Russ
Ich habe diesen Punkt in der Frage jetzt ein wenig näher ausgeführt. Ich kenne die damit verbundenen Risiken und für diese spezielle Anwendung wurde dies als akzeptabel angesehen. Und das Abbrechen der Anwendung für etwas so Einfaches wie einen Index außerhalb der Grenzen, während die Benutzeroberfläche versuchte, eine nette Animation zu erstellen, ist übertrieben und unnötig. Ja, ich kenne die genaue Ursache nicht, aber wir haben Daten, die die Behauptung stützen, dass die überwiegende Mehrheit der Fehlerfälle harmlos ist. Die schwerwiegenden, die wir maskieren, können zum Absturz der Anwendung führen, aber das wäre ohne globale Ausnahmebehandlung sowieso passiert.
Joey
Noch eine Randnotiz: Wenn Sie mit diesem Ansatz Abstürze verhindern, werden die Benutzer ihn höchstwahrscheinlich lieben.
Lahjaton_j
Siehe Windows-Behandlung nicht behandelter Ausnahmen in WPF (Die vollständigste Sammlung von Handlern) in C # für Visual Studio 2010 . Es enthält 5 Beispiele, darunter AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException und AppDomain.CurrentDomain.UnhandledException.
user34660
Ich möchte hinzufügen, dass ein VB-ähnlicher Code-Fluss von On Error Resume Nextin C # nicht möglich ist. Nach einem Exception(C # hat keine "Fehler") können Sie nicht einfach mit der nächsten Anweisung fortfahren: Die Ausführung wird in einem catchBlock fortgesetzt - oder in einem der in den folgenden Antworten beschriebenen Ereignishandler.
Mike

Antworten:

191

Verwenden Sie die Application.DispatcherUnhandledException Event. Eine Zusammenfassung finden Sie in dieser Frage (siehe Antwort von Drew Noakes ).

Beachten Sie, dass es immer noch Ausnahmen gibt, die eine erfolgreiche Wiederaufnahme Ihrer Anwendung ausschließen, z. B. nach einem Stapelüberlauf, erschöpftem Speicher oder einem Verlust der Netzwerkverbindung, während Sie versuchen, in der Datenbank zu speichern.

David Schmitt
quelle
Vielen Dank. Und ja, ich bin mir bewusst, dass es Ausnahmen gibt, von denen ich mich nicht erholen kann, aber die überwiegende Mehrheit der möglichen Ausnahmen ist in diesem Fall harmlos.
Joey
14
Wenn Ihre Ausnahme in einem Hintergrundthread auftritt (z. B. mithilfe von ThreadPool.QueueUserWorkItem), funktioniert dies nicht.
Szymon Rozga
@siz: guter Punkt, aber diese können mit einem normalen Try-Block im Workitem behandelt werden, nicht wahr?
David Schmitt
1
@PitiOngmongkolkul: Der Handler wird von Ihrer Hauptschleife als Ereignis aufgerufen. Wenn die Ereignishandler zurückkehren, wird Ihre App normal fortgesetzt.
David Schmitt
4
Es scheint, dass wir e.Handled = true setzen müssen, wobei e ein DispatcherUnhandledExceptionEventArgs ist, um den Standardhandler zu überspringen, der die Programme beendet. msdn.microsoft.com/en-us/library/…
Piti Ongmongkolkul
55

Beispielcode mit NLog , der Ausnahmen abfängt , die von allen Threads in der AppDomain , vom UI-Dispatcher-Thread und von den asynchronen Funktionen ausgelöst werden :

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ı
quelle
10
Dies ist die vollständigste Antwort hier! Taks Scheduler-Ausnahmen sind enthalten. Am besten für mich, sauberer und einfacher Code.
Nikola Jovic
3
Vor kurzem hatte ich eine App.config-Beschädigung bei einem Kunden, und ihre App wurde nicht einmal gestartet, weil NLog versuchte, aus App.config zu lesen, und eine Ausnahme auslöste. Da sich diese Ausnahme im statischen Logger-Initialisierer befand , wurde sie vom UnhandledExceptionHandler nicht abgefangen . Ich musste in Windows Event Log Viewer schauen, um herauszufinden, was los war ...
Heltonbiker
Ich empfehle zu Satz e.Handled = true;an die , UnhandledExceptiondass die Anwendung nicht auf UI Ausnahmen abstürzt
Apfelkuacha
30

AppDomain.UnhandledException- Ereignis

Dieses Ereignis informiert Sie über nicht erfasste Ausnahmen. Die Anwendung kann Informationen zu der Ausnahme protokollieren, bevor der Systemstandardhandler die Ausnahme an den Benutzer meldet und die Anwendung beendet.

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

Wenn das UnhandledException-Ereignis in der Standardanwendungsdomäne behandelt wird, wird es dort für jede nicht behandelte Ausnahme in einem Thread ausgelöst, unabhängig davon, in welcher Anwendungsdomäne der Thread gestartet wurde. Wenn der Thread in einer Anwendungsdomäne gestartet wurde, die über einen Ereignishandler für UnhandledException verfügt, Das Ereignis wird in dieser Anwendungsdomäne ausgelöst. Wenn diese Anwendungsdomäne nicht die Standardanwendungsdomäne ist und sich in der Standardanwendungsdomäne auch ein Ereignishandler befindet, wird das Ereignis in beiden Anwendungsdomänen ausgelöst.

Angenommen, ein Thread startet in der Anwendungsdomäne "AD1", ruft eine Methode in der Anwendungsdomäne "AD2" auf und ruft von dort eine Methode in der Anwendungsdomäne "AD3" auf, wo eine Ausnahme ausgelöst wird. Die erste Anwendungsdomäne, in der das UnhandledException-Ereignis ausgelöst werden kann, ist "AD1". Wenn diese Anwendungsdomäne nicht die Standardanwendungsdomäne ist, kann das Ereignis auch in der Standardanwendungsdomäne ausgelöst werden.

CharithJ
quelle
Kopieren von der URL, auf die Sie verwiesen haben (was meiner Meinung nach nur von Konsolen-Apps und WinForms-Apps spricht): "Ab .NET Framework 4 wird dieses Ereignis nicht für Ausnahmen ausgelöst, die den Status des Prozesses beschädigen, z. B. Stapelüberläufe oder Zugriffsverletzungen, es sei denn, der Ereignishandler ist sicherheitskritisch und verfügt über das Attribut HandleProcessCorruptedStateExceptionsAttribute. "
George Birbilis
1
@GeorgeBirbilis: Sie können das UnhandledException-Ereignis im App-Konstruktor abonnieren.
CharithJ
18

Beachten Sie zusätzlich zu dem, was andere hier erwähnt haben, dass das Application.DispatcherUnhandledException(und seine Ähnlichkeiten ) mit kombiniert wird

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

in der app.configverhindert, dass Ihre sekundäre Thread-Ausnahme die Anwendung herunterfährt.

Hertzel Guinness
quelle
2

Hier ist ein vollständiges Beispiel mit 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);
        }        
    }


}
Entwickler
quelle
-4

Wie "VB's On Error Resume Next?" Das klingt irgendwie beängstigend. Die erste Empfehlung ist, es nicht zu tun. Die zweite Empfehlung ist, es nicht zu tun und nicht darüber nachzudenken. Sie müssen Ihre Fehler besser isolieren. Wie Sie dieses Problem angehen können, hängt davon ab, wie Ihr Code strukturiert ist. Wenn Sie ein Muster wie MVC oder ähnliches verwenden, sollte dies nicht zu schwierig sein und würde definitiv keinen globalen Ausnahme-Schlucker erfordern. Suchen Sie zweitens nach einer guten Protokollierungsbibliothek wie log4net oder verwenden Sie die Ablaufverfolgung. Wir müssen mehr Details wissen, z. B. über welche Arten von Ausnahmen Sie sprechen und welche Teile Ihrer Anwendung dazu führen können, dass Ausnahmen ausgelöst werden.

BobbyShaftoe
quelle
2
Der Punkt ist, dass ich die Anwendung auch nach nicht erfassten Ausnahmen am Laufen halten möchte. Ich möchte sie nur protokollieren (wir haben ein benutzerdefiniertes Protokollierungsframework dafür, basierend auf unseren Anforderungen) und muss nicht das gesamte Programm abbrechen, nur weil ein Plugin etwas Seltsames in seinem Benutzerinteraktionscode getan hat. Nach dem, was ich bisher gesehen habe, ist es nicht trivial, die Ursachen sauber zu isolieren, da sich die verschiedenen Teile nicht wirklich kennen.
Joey
9
Ich verstehe, aber Sie müssen sehr vorsichtig sein, nach einer Ausnahme, die Sie nicht untersuchen, fortzufahren. Es besteht die Möglichkeit einer Datenkorruption und so weiter.
BobbyShaftoe
3
Ich stimme BobbyShaftoe zu. Dies ist nicht der richtige Weg. Lassen Sie die Anwendung abstürzen. Wenn der Fehler bei einem Plugin liegt, beheben Sie das Plugin oder lassen Sie es von jemandem reparieren. Es am Laufen zu lassen ist EXTREM gefährlich. Sie werden seltsame Nebenwirkungen bekommen und einen Punkt erreichen, an dem Sie keine logische Erklärung für die Fehler finden, die in Ihrer Anwendung auftreten.
1
Ich habe diesen Punkt in der Frage jetzt ein wenig näher ausgeführt. Ich kenne die damit verbundenen Risiken und für diese spezielle Anwendung wurde dies als akzeptabel angesehen. Und das Abbrechen der Anwendung für etwas so Einfaches wie einen Index außerhalb der Grenzen, während die Benutzeroberfläche versuchte, eine nette Animation zu erstellen, ist übertrieben und unnötig. Ja, ich kenne die genaue Ursache nicht, aber wir haben Daten, die die Behauptung stützen, dass die überwiegende Mehrheit der Fehlerfälle harmlos ist. Die schwerwiegenden, die wir maskieren, können zum Absturz der Anwendung führen, aber das wäre ohne globale Ausnahmebehandlung sowieso passiert.
Joey
Dies sollte ein Kommentar sein, keine Antwort. Die Antwort auf "Wie mache ich das?" Mit "Sie sollten es wahrscheinlich nicht tun" ist keine Antwort, sondern ein Kommentar.
Josh Noe