Глобальный обработчик исключений .NET в консольном приложении

199

Вопрос: я хочу определить глобальный обработчик исключений для необработанных исключений в моем консольном приложении. В asp.net его можно определить в global.asax, а в приложениях / службах windows - как ниже.

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

Но как я могу определить глобальный обработчик исключений для консольного приложения?
currentDomain, кажется, не работает (.NET 2.0)?

Редактировать:

Ага, глупая ошибка.
В VB.NET нужно добавить ключевое слово «AddHandler» перед currentDomain, иначе вы не увидите событие UnhandledException в IntelliSense ...
Это потому, что компиляторы VB.NET и C # обрабатывают обработку событий по-разному.

Стефан Штайгер
источник

Ответы:

283

Нет, это правильный способ сделать это. Это сработало точно так, как и должно, что-то, из чего вы можете работать, возможно:

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

Имейте в виду, что таким способом вы не можете отловить исключения загрузки типов и файлов, генерируемые джиттером. Они происходят до того, как ваш метод Main () начнет работать. Для того, чтобы отловить это, нужно отложить дрожание, переместить рискованный код в другой метод и применить к нему атрибут [MethodImpl (MethodImplOptions.NoInlining)].

Ганс Пассант
источник
3
Я реализовал то, что вы предложили здесь, но я не хочу выходить из приложения. Я просто хочу зарегистрировать его и продолжить процесс (без Console.ReadLine()каких-либо других нарушений потока программ. Но я получаю повторное повышение исключения снова и снова и снова.
3
@ Shahrooz Jefri: Вы не можете продолжить, как только получите необработанное исключение. Стек испорчен, и это терминал. Если у вас есть сервер, то в UnhandledExceptionTrapper вы можете перезапустить программу с теми же аргументами командной строки.
Стефан Штайгер
6
Это, безусловно, делает! Мы не говорим о событии Application.ThreadException здесь.
Ганс Пассант
4
Я думаю, что ключом к пониманию этого ответа и комментариев к нему является осознание того, что этот код демонстрирует теперь обнаружение исключения, но не полностью «обрабатывает» его, как вы могли бы в обычном блоке try / catch, где у вас есть возможность продолжить , Смотрите мой другой ответ для деталей. Если вы «обрабатываете» исключение таким образом, у вас нет возможности продолжить выполнение, когда исключение возникает в другом потоке. В этом смысле можно сказать, что этот ответ не полностью «обрабатывает» исключение, если под «обработать» вы подразумеваете «обрабатывать и продолжать работу».
BlueMonkMN
4
Не говоря уже о том, что существует множество технологий для работы в разных потоках. Например, Task Parallel Library (TPL) имеет свой собственный способ перехвата необработанных исключений. Поэтому говорить о том, что это не работает для всех ситуаций, довольно смешно: в C # нет единого универсального средства для всего, но в зависимости от технологий, которые вы используете, существуют различные места, в которые вы можете зацепиться.
Дуг
23

Если у вас однопоточное приложение, вы можете использовать простую попытку / перехват в функции Main, однако это не распространяется на исключения, которые могут быть выброшены вне функции Main, например, в других потоках (как отмечено в другом Комментарии). Этот код демонстрирует, как исключение может привести к завершению работы приложения, даже если вы пытались обработать его в Main (обратите внимание, как программа корректно завершает работу, если вы нажмете Enter и разрешите приложению корректно завершить работу до возникновения исключения, но если вы позволите ему работать оканчивается довольно несчастно)

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

Вы можете получить уведомление о том, что другой поток выдает исключение для выполнения некоторой очистки до выхода из приложения, но, насколько я могу судить, из консольного приложения нельзя заставить приложение продолжить работу, если вы не обработали исключение в потоке, из которого он создается без использования каких-либо непонятных параметров совместимости, чтобы приложение работало так же, как в .NET 1.x. Этот код демонстрирует, как основной поток может быть уведомлен об исключениях, поступающих из других потоков, но все равно будет прерван к несчастью:

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

Поэтому, на мой взгляд, самый простой способ справиться с этим в консольном приложении - убедиться, что каждый поток имеет обработчик исключений на корневом уровне:

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
источник
TRY CATCH не работает в режиме выпуска для непредвиденных ошибок: /
Muflix
12

Вам также нужно обрабатывать исключения из потоков:

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

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

Ой, простите, это было для winforms, для любых потоков, которые вы используете в консольном приложении, вам придется заключить в блок try / catch. Фоновые потоки, которые сталкиваются с необработанными исключениями, не вызывают завершение приложения.

Черный лед
источник
1

Я только что унаследовал старое консольное приложение VB.NET и мне нужно было настроить Global Exception Handler. Поскольку в этом вопросе несколько раз упоминается VB.NET и он помечен VB.NET, но все остальные ответы здесь приведены на C #, я подумал, что добавлю точный синтаксис и для приложения 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

Я использовал REMмаркер комментария вместо единственной кавычки, потому что переполнение стека, казалось, справлялось с подсветкой синтаксиса немного лучше REM.

JustSomeDude
источник
-13

То, что вы пытаетесь, должно работать в соответствии с документами MSDN для .Net 2.0. Вы также можете попробовать попробовать / поймать прямо в главном возле вашей точки входа для консольного приложения.

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

И теперь ваш улов будет обрабатывать все, что не поймано ( в основном потоке ). Это может быть изящно и даже перезапустить, где это было, если вы хотите, или вы можете просто позволить приложению умереть и зарегистрировать исключение. Вы можете добавить, наконец, если вы хотите сделать какую-либо уборку. Каждому потоку потребуется собственная обработка исключений высокого уровня, аналогичная основной.

Отредактировано, чтобы прояснить вопрос о потоках, как указано BlueMonkMN и подробно показано в его ответе.

Родни С. Фоли
источник
1
К сожалению, на самом деле исключения могут по-прежнему создаваться вне блока Main (). На самом деле это не «ловит все», как вы думаете. Смотрите ответ @Hans.
Майк Атлас
@ Майк Сначала я сказал, что он делает это правильно, и что он может попробовать попробовать / поймать в основном. Я не уверен, почему вы (или кто-то еще) отказали мне в голосовании, когда я согласился с Гансом, просто предоставив другой ответ, который я не ожидал получить чек. Это не совсем справедливо, а затем сказать, что альтернатива неверна, не предоставив никаких доказательств того, как исключение, которое может быть перехвачено процессом AppDomain UnhandledException, не может перехватить попытка / перехват в Main. Я считаю грубым говорить, что что-то не так, не доказывая, почему это неправильно, просто говорить, что это так, но не делает этого.
Родни С. Фоли
5
Я опубликовал пример, который вы просите. Пожалуйста, будьте ответственны и удалите свои нерелевантные отрицательные голоса из старых ответов Майка, если вы этого не сделали. (Нет личного интереса, просто не нравится видеть такое злоупотребление системой.)
BlueMonkMN
3
Тем не менее, вы все еще играете в ту же «игру», в которую он играет, только в худшей форме, потому что это чистый ответный удар, не основанный на качестве ответа. Это не способ решить проблему, только усугубить ее. Это особенно плохо, когда вы принимаете ответные меры против кого-то, кто даже имел законную озабоченность вашим ответом (как я продемонстрировал).
BlueMonkMN
3
О, я бы также добавил, что понижающее голосование предназначено не для людей, которые «являются полным идиотом или нарушают правила», а скорее для оценки качества ответа. Мне кажется, что ответы с отрицательным голосованием для того, чтобы «прокомментировать» лицо, предоставляющее их, являются гораздо большим злоупотреблением, чем ответы с отрицательным голосованием, основанные на содержании самого ответа, независимо от того, является ли этот голос точным или нет. Не принимайте / делайте это настолько личным.
BlueMonkMN