.NET Global Exception Handler in der Konsolenanwendung

198

Frage: Ich möchte einen globalen Ausnahmebehandler für nicht behandelte Ausnahmen in meiner Konsolenanwendung definieren. In asp.net kann man eine in global.asax definieren, und in Windows-Anwendungen / -Diensten kann man wie folgt definieren

AppDomain currentDomain = AppDomain.CurrentDomain;
currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyExceptionHandler);

Aber wie kann ich einen globalen Ausnahmebehandler für eine Konsolenanwendung definieren?
currentDomain scheint nicht zu funktionieren (.NET 2.0)?

Bearbeiten:

Argh, dummer Fehler.
In VB.NET muss das Schlüsselwort "AddHandler" vor currentDomain hinzugefügt werden, sonst wird das UnhandledException-Ereignis in IntelliSense nicht angezeigt. Dies liegt daran, dass die VB.NET-
und C # -Compiler die Ereignisbehandlung unterschiedlich behandeln.

Stefan Steiger
quelle

Antworten:

283

Nein, das ist der richtige Weg. Dies hat genau so funktioniert, wie es sollte, woraus Sie vielleicht arbeiten können:

using System;

class Program {
    static void Main(string[] args) {
        System.AppDomain.CurrentDomain.UnhandledException += UnhandledExceptionTrapper;
        throw new Exception("Kaboom");
    }

    static void UnhandledExceptionTrapper(object sender, UnhandledExceptionEventArgs e) {
        Console.WriteLine(e.ExceptionObject.ToString());
        Console.WriteLine("Press Enter to continue");
        Console.ReadLine();
        Environment.Exit(1);
    }
}

Beachten Sie, dass Sie auf diese Weise keine vom Jitter generierten Ausnahmen beim Laden von Typen und Dateien abfangen können. Sie treten auf, bevor Ihre Main () -Methode ausgeführt wird. Um diese abzufangen, muss der Jitter verzögert, der riskante Code in eine andere Methode verschoben und das Attribut [MethodImpl (MethodImplOptions.NoInlining)] darauf angewendet werden.

Hans Passant
quelle
3
Ich habe implementiert, was Sie hier vorgeschlagen haben, aber ich möchte die Anwendung nicht beenden. Ich möchte es nur protokollieren und den Prozess fortsetzen (ohne Console.ReadLine()oder irgendeine andere Störung des Programmflusses. Aber was ich bekomme, ist die Ausnahme, die immer wieder und immer wieder
3
@ Shahrooz Jefri: Sie können nicht fortfahren, wenn Sie eine nicht behandelte Ausnahme erhalten. Der Stapel ist durcheinander und dies ist ein Terminal. Wenn Sie einen Server haben, können Sie in UnhandledExceptionTrapper das Programm mit denselben Befehlszeilenargumenten neu starten.
Stefan Steiger
6
Das tut es mit Sicherheit! Wir sprechen hier nicht über das Application.ThreadException-Ereignis.
Hans Passant
4
Ich denke, der Schlüssel zum Verständnis dieser Antwort und der Kommentare in der Antwort besteht darin, zu erkennen, dass dieser Code jetzt demonstriert, um die Ausnahme zu erkennen, sie jedoch nicht vollständig "zu behandeln", wie Sie es in einem normalen Try / Catch-Block tun könnten, in dem Sie die Option haben, fortzufahren . Siehe meine andere Antwort für Details. Wenn Sie die Ausnahme auf diese Weise "behandeln", haben Sie keine Möglichkeit, die Ausführung fortzusetzen, wenn eine Ausnahme in einem anderen Thread auftritt. In diesem Sinne kann gesagt werden, dass diese Antwort die Ausnahme nicht vollständig "behandelt", wenn Sie mit "behandeln" "verarbeiten und weiter ausführen" meinen.
BlueMonkMN
4
Ganz zu schweigen davon, dass es viele Technologien gibt, die in verschiedenen Threads ausgeführt werden können. Beispielsweise hat die Task Parallel Library (TPL) eine eigene Methode, um nicht behandelte Ausnahmen abzufangen. Zu sagen, dass dies nicht in allen Situationen funktioniert, ist lächerlich. In C # gibt es KEINEN Allrounder für alles, aber abhängig von den von Ihnen verwendeten Technologien gibt es verschiedene Orte, an die Sie sich einhängen können.
Doug
23

Wenn Sie eine Single-Threaded-Anwendung haben, können Sie einen einfachen Versuch / Fang in der Hauptfunktion verwenden. Dies gilt jedoch nicht für Ausnahmen, die außerhalb der Hauptfunktion ausgelöst werden können, z. B. in anderen Threads (wie in anderen angegeben) Bemerkungen). Dieser Code zeigt, wie eine Ausnahme dazu führen kann, dass die Anwendung beendet wird, obwohl Sie versucht haben, sie in Main zu behandeln (beachten Sie, wie das Programm ordnungsgemäß beendet wird, wenn Sie die Eingabetaste drücken und die Anwendung ordnungsgemäß beenden, bevor die Ausnahme auftritt, aber wenn Sie sie ausführen lassen , es endet ziemlich unglücklich):

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void DemoThread()
{
   for(int i = 5; i >= 0; i--)
   {
      Console.Write("24/{0} =", i);
      Console.Out.Flush();
      Console.WriteLine("{0}", 24 / i);
      System.Threading.Thread.Sleep(1000);
      if (exiting) return;
   }
}

Sie können eine Benachrichtigung erhalten, wenn ein anderer Thread eine Ausnahme auslöst, um eine Bereinigung durchzuführen, bevor die Anwendung beendet wird. Soweit ich jedoch feststellen kann, können Sie von einer Konsolenanwendung aus die Ausführung der Anwendung nicht erzwingen, wenn Sie die Ausnahme nicht behandeln auf dem Thread, von dem es geworfen wird, ohne einige obskure Kompatibilitätsoptionen zu verwenden, damit sich die Anwendung so verhält, wie es mit .NET 1.x der Fall wäre. Dieser Code zeigt, wie der Hauptthread über Ausnahmen von anderen Threads benachrichtigt werden kann, aber dennoch unglücklich beendet wird:

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
   Console.WriteLine("Notified of a thread exception... application is terminating.");
}

static void DemoThread()
{
   for(int i = 5; i >= 0; i--)
   {
      Console.Write("24/{0} =", i);
      Console.Out.Flush();
      Console.WriteLine("{0}", 24 / i);
      System.Threading.Thread.Sleep(1000);
      if (exiting) return;
   }
}

Meiner Meinung nach besteht die sauberste Möglichkeit, in einer Konsolenanwendung damit umzugehen, darin, sicherzustellen, dass jeder Thread einen Ausnahmehandler auf Stammebene hat:

static bool exiting = false;

static void Main(string[] args)
{
   try
   {
      System.Threading.Thread demo = new System.Threading.Thread(DemoThread);
      demo.Start();
      Console.ReadLine();
      exiting = true;
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception");
   }
}

static void DemoThread()
{
   try
   {
      for (int i = 5; i >= 0; i--)
      {
         Console.Write("24/{0} =", i);
         Console.Out.Flush();
         Console.WriteLine("{0}", 24 / i);
         System.Threading.Thread.Sleep(1000);
         if (exiting) return;
      }
   }
   catch (Exception ex)
   {
      Console.WriteLine("Caught an exception on the other thread");
   }
}
BlueMonkMN
quelle
TRY CATCH funktioniert nicht im Release-Modus für unerwartete Fehler: /
Muflix
12

Sie müssen auch Ausnahmen von Threads behandeln:

static void Main(string[] args) {
Application.ThreadException += MYThreadHandler;
}

private void MYThreadHandler(object sender, Threading.ThreadExceptionEventArgs e)
{
    Console.WriteLine(e.Exception.StackTrace);
}

Tut mir leid, das war für Winforms, für alle Threads, die Sie in einer Konsolenanwendung verwenden, müssen Sie einen Try / Catch-Block einschließen. Hintergrundthreads, bei denen nicht behandelte Ausnahmen auftreten, führen nicht zum Beenden der Anwendung.

BlackICE
quelle
1

Ich habe gerade eine alte VB.NET-Konsolenanwendung geerbt und musste einen Global Exception Handler einrichten. Da diese Frage VB.NET einige Male erwähnt und mit VB.NET markiert ist, aber alle anderen Antworten hier in C # sind, dachte ich, ich würde die genaue Syntax auch für eine VB.NET-Anwendung hinzufügen.

Public Sub Main()
    REM Set up Global Unhandled Exception Handler.
    AddHandler System.AppDomain.CurrentDomain.UnhandledException, AddressOf MyUnhandledExceptionEvent

    REM Do other stuff
End Sub

Public Sub MyUnhandledExceptionEvent(ByVal sender As Object, ByVal e As UnhandledExceptionEventArgs)
    REM Log Exception here and do whatever else is needed
End Sub

Ich habe hier den REMKommentar-Marker anstelle des einfachen Anführungszeichens verwendet, da Stack Overflow die Syntax-Hervorhebung etwas besser zu handhaben schien REM.

JustSomeDude
quelle
-13

Was Sie versuchen, sollte gemäß den MSDN-Dokumenten für .Net 2.0 funktionieren. Sie können auch versuchen, direkt in der Nähe Ihres Einstiegspunkts für die Konsolen-App zu versuchen / zu fangen.

static void Main(string[] args)
{
    try
    {
        // Start Working
    }
    catch (Exception ex)
    {
        // Output/Log Exception
    }
    finally
    {
        // Clean Up If Needed
    }
}

Und jetzt behandelt Ihr Fang alles, was nicht gefangen wurde ( im Haupt-Thread ). Es kann elegant sein und sogar neu starten, wo es war, wenn Sie möchten, oder Sie können die App einfach sterben lassen und die Ausnahme protokollieren. Sie würden endlich eine hinzufügen, wenn Sie aufräumen wollten. Jeder Thread erfordert eine eigene Ausnahmebehandlung auf hoher Ebene, ähnlich der Hauptthread.

Bearbeitet, um den Punkt über Threads zu verdeutlichen, auf den BlueMonkMN hingewiesen und in seiner Antwort ausführlich gezeigt hat.

Rodney S. Foley
quelle
1
Ausnahmen können leider immer noch außerhalb des Main () - Blocks geworfen werden. Dies ist eigentlich kein "Fang alle", wie Sie vielleicht denken. Siehe die Antwort von @Hans.
Mike Atlas
@ Mike Zuerst sagte ich, dass die Art und Weise, wie er es tut, richtig ist und dass er einen Versuch / Fang in der Hauptsache versuchen könnte. Ich bin mir nicht sicher, warum Sie (oder jemand anderes) mir eine Stimme gegeben haben, als ich mit Hans einverstanden war und nur eine weitere Antwort gab, für die ich keinen Scheck erwartet hatte. Das ist nicht wirklich fair, und dann zu sagen, dass die Alternative falsch ist, ohne einen Beweis dafür zu liefern, wie eine Ausnahme, die vom AppDomain UnhandledException-Prozess abgefangen werden kann, die ein Versuch / Fang in Main nicht abfangen kann. Ich finde es unhöflich zu sagen, dass etwas nicht stimmt, ohne zu beweisen, warum es falsch ist. Nur zu sagen, dass es so ist, macht es nicht so.
Rodney S. Foley
5
Ich habe das Beispiel gepostet, nach dem Sie fragen. Bitte seien Sie verantwortlich und entfernen Sie Ihre irrelevanten Abstimmungen aus Mikes alten Antworten, wenn Sie dies nicht getan haben. (Kein persönliches Interesse, mag es einfach nicht, einen solchen Missbrauch des Systems zu sehen.)
BlueMonkMN
3
Trotzdem spielen Sie immer noch das gleiche "Spiel" wie er, nur auf eine schlechtere Weise, weil es reine Vergeltung ist, die nicht auf der Qualität einer Antwort basiert. Das ist kein Weg, um das Problem zu lösen, sondern es nur noch schlimmer zu machen. Es ist besonders schlimm, wenn Sie sich an jemandem rächen, der sogar berechtigte Bedenken hinsichtlich Ihrer Antwort hatte (wie ich gezeigt habe).
BlueMonkMN
3
Oh, ich möchte auch hinzufügen, dass Down-Voting nicht für Leute gedacht ist, die "ein kompletter Idiot sind oder gegen die Regeln verstoßen", sondern um die Qualität einer Antwort zu beurteilen. Es scheint mir, dass Abstimmungsantworten, um die Person, die sie bereitstellt, zu "kommentieren", einen viel größeren Missbrauch darstellen als Abstimmungsantworten, die auf dem Inhalt der Antwort selbst basieren, unabhängig davon, ob diese Abstimmung korrekt ist oder nicht. Nimm es nicht so persönlich.
BlueMonkMN