Globalny moduł obsługi wyjątków .NET w aplikacji konsoli

198

Pytanie: Chcę zdefiniować globalny moduł obsługi wyjątków dla nieobsługiwanych wyjątków w mojej aplikacji konsoli. W asp.net można zdefiniować jeden w global.asax, a w aplikacjach / usługach Windows można zdefiniować jak poniżej

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

Ale jak mogę zdefiniować globalny moduł obsługi wyjątków dla aplikacji konsoli?
currentDomain wydaje się nie działać (.NET 2.0)?

Edytować:

Argh, głupi błąd.
W VB.NET należy dodać słowo kluczowe „AddHandler” przed currentDomain, w przeciwnym razie nie zobaczysz zdarzenia UnhandledException w IntelliSense ...
To dlatego, że kompilatory VB.NET i C # traktują obsługę zdarzeń inaczej.

Stefan Steiger
źródło

Odpowiedzi:

283

Nie, to właściwy sposób na zrobienie tego. Działa to dokładnie tak, jak powinno, z czego możesz pracować, być może:

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

Należy pamiętać, że nie można wychwycić wyjątków typu i ładowania plików generowanych przez jitter w ten sposób. Występują przed uruchomieniem metody Main (). Złapanie tych wymaga opóźnienia jittera, przenieś ryzykowny kod do innej metody i zastosuj do niego atrybut [MethodImpl (MethodImplOptions.NoInlining)].

Hans Passant
źródło
3
Zaimplementowałem to, co tu zaproponowałeś, ale nie chcę wychodzić z aplikacji. Chcę tylko zalogować się i kontynuować proces (bez Console.ReadLine()zakłóceń lub innych zakłóceń w przebiegu programu. Ale dostaję wyjątek ponownie zgłaszany raz po raz)
3
@Shahrooz Jefri: Nie możesz kontynuować, gdy otrzymasz nieobsługiwany wyjątek. Stos jest zawalony, a to jest terminal. Jeśli masz serwer, w UnhandledExceptionTrapper możesz zrestartować program z tymi samymi argumentami wiersza poleceń.
Stefan Steiger,
6
Z pewnością tak! Nie mówimy tutaj o zdarzeniu Application.ThreadException.
Hans Passant,
4
Myślę, że kluczem do zrozumienia tej odpowiedzi i komentarzy w odpowiedzi jest uświadomienie sobie, że ten kod pokazuje teraz, że wykrywa wyjątek, ale nie w pełni go „obsługuje”, jak w normalnym bloku try / catch, w którym można kontynuować . Zobacz moją drugą odpowiedź, aby poznać szczegóły. Jeśli „obsłużysz” wyjątek w ten sposób, nie będziesz mieć możliwości kontynuowania działania, gdy wyjątek wystąpi w innym wątku. W tym sensie można powiedzieć, że ta odpowiedź nie „w pełni obsłuży” wyjątek, jeśli przez „obsłużyć” rozumiesz „przetwarzaj i kontynuuj działanie”.
BlueMonkMN
4
Nie wspominając, że istnieje wiele technologii do uruchomienia w różnych wątkach. Na przykład biblioteka zadań równoległych (TPL) ma swój własny sposób wychwytywania nieobsługiwanych wyjątków. Mówienie, że to nie działa we wszystkich sytuacjach, jest trochę absurdalne, NIE ma jednego miejsca dla wszystkiego w C #, ale w zależności od używanych technologii istnieją różne miejsca, w których można się podłączyć.
Doug
23

Jeśli masz aplikację jednowątkową, możesz użyć prostej try / catch w funkcji Main, jednak nie obejmuje to wyjątków, które mogą być zgłaszane poza funkcją Main, na przykład w innych wątkach (jak zaznaczono w innych komentarze). Ten kod pokazuje, w jaki sposób wyjątek może spowodować zamknięcie aplikacji, nawet jeśli próbujesz obsłużyć go w Main (zwróć uwagę, jak program kończy działanie z wdziękiem, jeśli naciśniesz enter i zezwolisz aplikacji z wdziękiem wyjść, zanim wystąpi wyjątek, ale jeśli pozwolisz mu działać) , kończy się dość niefortunnie):

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

Możesz otrzymać powiadomienie, kiedy inny wątek zgłosi wyjątek, aby wykonać pewne czyszczenie przed zamknięciem aplikacji, ale o ile mogę stwierdzić, nie możesz, z aplikacji konsoli, zmusić aplikacji do kontynuowania działania, jeśli nie obsłużysz wyjątku w wątku, z którego jest wyrzucany bez użycia niejasnych opcji zgodności, aby aplikacja działała tak, jak w przypadku platformy .NET 1.x. Ten kod pokazuje, w jaki sposób główny wątek może być powiadamiany o wyjątkach pochodzących z innych wątków, ale nadal niefortunnie kończy się:

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

Moim zdaniem najczystszym sposobem obsługi tego w aplikacji konsolowej jest upewnienie się, że każdy wątek ma moduł obsługi wyjątków na poziomie głównym:

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
źródło
TRY CATCH nie działa w trybie zwolnienia dla nieoczekiwanych błędów: /
Muflix
12

Musisz także obsługiwać wyjątki od wątków:

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

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

Ups, przepraszam, że dotyczyło to formularzy win. W przypadku wszystkich wątków używanych w aplikacji konsolowej będziesz musiał zawrzeć blok try / catch. Wątki w tle, które napotykają nieobsługiwane wyjątki, nie powodują zakończenia działania aplikacji.

Czarny lód
źródło
1

Właśnie odziedziczyłem starą aplikację konsoli VB.NET i potrzebowałem skonfigurować Global Exception Handler. Ponieważ to pytanie wspomina VB.NET kilka razy i jest oznaczone VB.NET, ale wszystkie inne odpowiedzi tutaj są w języku C #, pomyślałem, że dodam dokładną składnię również dla aplikacji VB.NET.

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

Użyłem tutaj REMznacznika komentarza zamiast pojedynczego cudzysłowu, ponieważ Stack Overflow zdawał się lepiej obsługiwać podświetlanie składni REM.

JustSomeDude
źródło
-13

To, co próbujesz, powinno działać zgodnie z dokumentacją MSDN dla .Net 2.0. Możesz także spróbować spróbować / złapać bezpośrednio wokół punktu wejścia do aplikacji konsoli.

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

A teraz Twój haczyk poradzi sobie z wszystkim, co nie zostało złapane ( w głównym wątku ). Może być pełen wdzięku, a nawet uruchomić ponownie, jeśli chcesz, lub możesz po prostu pozwolić aplikacji umrzeć i zarejestrować wyjątek. Dodałbyś wreszcie, jeśli chcesz zrobić porządki. Każdy wątek będzie wymagał obsługi wysokiego poziomu wyjątków podobnych do głównego.

Zredagowane, aby wyjaśnić punkt dotyczący wątków, jak wskazał BlueMonkMN i szczegółowo pokazał w swojej odpowiedzi.

Rodney S. Foley
źródło
1
Niestety wyjątki wciąż mogą zostać wyrzucone poza blok Main (). W rzeczywistości nie jest to „złapać wszystko”, jak mogłoby się wydawać. Zobacz odpowiedź @Hans.
Mike Atlas,
@Mike Najpierw powiedziałem, że sposób, w jaki to robi, jest poprawny i że może spróbować spróbować / złapać w Main. Nie jestem pewien, dlaczego ty (lub ktoś inny) oddałeś mi głos, kiedy zgadzałem się z Hansem, po prostu udzielając kolejnej odpowiedzi, na którą nie spodziewałem się otrzymać czeku. To nie jest do końca uczciwe, a następnie powiedzieć, że alternatywa jest zła, nie dostarczając żadnego dowodu, w jaki sposób wyjątek, który może zostać przechwycony przez proces AppDomain UnhandledException, którego nie może złapać próba / złapanie w Main. Uważam, że niegrzeczne jest mówienie, że coś jest nie tak, nie udowadniając, dlaczego tak jest, po prostu powiedzenie, że tak jest, nie czyni tego.
Rodney S. Foley
5
Zamieściłem przykład, o który prosisz. Bądź odpowiedzialny i usuń swoje nieistotne głosy ze starych odpowiedzi Mike'a, jeśli nie masz. (Brak osobistego zainteresowania, po prostu nie lubię takich nadużyć systemu.)
BlueMonkMN
3
Jednak nadal grasz w tę samą „grę”, którą on robi, tylko w gorszym sensie, ponieważ jest to czysty odwet, nie oparty na jakości odpowiedzi. To nie jest sposób na rozwiązanie problemu, tylko go pogorszenie. Jest to szczególnie złe, gdy podejmujesz działania odwetowe wobec osoby, która miała uzasadnione obawy dotyczące Twojej odpowiedzi (jak wykazałem).
BlueMonkMN
3
Och, dodałbym również, że głosowanie w dół nie jest przeznaczone dla ludzi, którzy „są kompletnymi idiotami lub łamią zasady”, ale raczej oceniają jakość odpowiedzi. Wydaje mi się, że odpowiedzi na głosowanie w dół w celu „skomentowania” osoby, która je przekazała, są o wiele większym nadużyciem niż odpowiedzi w głosowaniu w dół na podstawie treści samej odpowiedzi, niezależnie od tego, czy głosowanie jest dokładne, czy nie. Nie bierz / nie rób tego tak osobiście.
BlueMonkMN