Manipulador de exceção global do .NET no aplicativo de console

198

Pergunta: Desejo definir um manipulador de exceção global para exceções não tratadas no meu aplicativo de console. No asp.net, pode-se definir um em global.asax e, em aplicativos / serviços do Windows, pode-se definir como abaixo

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

Mas como posso definir um manipulador de exceção global para um aplicativo de console?
currentDomain parece não funcionar (.NET 2.0)?

Editar:

Argh, erro estúpido.
No VB.NET, é necessário adicionar a palavra-chave "AddHandler" na frente do currentDomain, caso contrário, não será exibido o evento UnhandledException no IntelliSense ...
Isso ocorre porque os compiladores VB.NET e C # tratam o tratamento de eventos de maneira diferente.

Stefan Steiger
fonte

Respostas:

283

Não, essa é a maneira correta de fazer isso. Isso funcionou exatamente como deveria, algo em que você pode trabalhar:

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

Lembre-se de que não é possível capturar exceções de tipo e carregamento de arquivo geradas pelo jitter dessa maneira. Eles acontecem antes que o método Main () comece a ser executado. Capturar esses requer atrasar a instabilidade, mova o código arriscado para outro método e aplique o atributo [MethodImpl (MethodImplOptions.NoInlining)] a ele.

Hans Passant
fonte
3
Eu implementei o que você propôs aqui, mas não quero sair do aplicativo. Eu só quero registrá-lo, e continuar o processo (sem Console.ReadLine()ou qualquer outra perturbação do fluxo de programa, mas o que eu recebo é a exceção re-levantar de novo e de novo, e de novo..
3
@ Shahrooz Jefri: Você não pode continuar depois de receber uma exceção não tratada. A pilha está desarrumada, e isso é terminal. Se você possui um servidor, o que você pode fazer em UnhandledExceptionTrapper é reiniciar o programa com os mesmos argumentos da linha de comando.
quer
6
Certamente faz! Não estamos falando sobre o evento Application.ThreadException aqui.
Hans Passant
4
Eu acho que a chave para entender essa resposta e os comentários em resposta é perceber que esse código demonstra agora para detectar a exceção, mas não a "manipula totalmente" como você pode em um bloco de tentativa / captura normal, onde você tem a opção de continuar . Veja minha outra resposta para obter detalhes. Se você "manipular" a exceção dessa maneira, não terá opção para continuar executando quando ocorrer uma exceção em outro encadeamento. Nesse sentido, pode-se dizer que esta resposta não "manipula" completamente a exceção se por "manipular" você quer dizer "processar e continuar executando".
precisa saber é o seguinte
4
Sem mencionar que existem muitas tecnologias para executar em diferentes threads. Por exemplo, a Task Parallel Library (TPL) tem seu próprio caminho para capturar exceções não tratadas. Portanto, dizer que isso não funciona para todas as situações é meio ridículo, NÃO existe um lugar para tudo em C #, mas dependendo das tecnologias usadas, existem vários lugares em que você pode se conectar.
Doug
23

Se você tiver um aplicativo de thread único, poderá usar uma simples tentativa / captura na função Principal, no entanto, isso não cobre exceções que podem ser lançadas fora da função Principal, em outros threads, por exemplo (como observado em outros comentários). Este código demonstra como uma exceção pode causar o encerramento do aplicativo, mesmo que você tenha tentado manipulá-lo no Main (observe como o programa sai normalmente se você pressionar enter e permitir que o aplicativo saia normalmente antes que a exceção ocorra, mas se você permitir a execução , termina bastante infeliz):

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

Você pode receber uma notificação de quando outro encadeamento lança uma exceção para executar alguma limpeza antes da saída do aplicativo, mas, pelo que sei, não é possível, a partir de um aplicativo de console, forçar o aplicativo a continuar em execução se você não manipular a exceção no encadeamento do qual ele é lançado sem usar algumas opções de compatibilidade obscuras para fazer com que o aplicativo se comporte como no .NET 1.x. Este código demonstra como o thread principal pode ser notificado sobre exceções provenientes de outros threads, mas ainda será finalizado infelizmente:

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

Portanto, na minha opinião, a maneira mais limpa de lidar com isso em um aplicativo de console é garantir que cada encadeamento tenha um manipulador de exceções no nível raiz:

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
fonte
TRY CATCH não funciona no modo de versão para erros inesperados: /
Muflix
12

Você também precisa lidar com exceções de threads:

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

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

Opa, desculpe pelo uso do winforms, para todos os threads que você estiver usando em um aplicativo de console, será necessário incluir um bloco try / catch. Os encadeamentos em segundo plano que encontram exceções sem tratamento não fazem com que o aplicativo seja finalizado.

Gelo preto
fonte
1

Acabei de herdar um aplicativo de console antigo do VB.NET e precisava configurar um manipulador de exceção global. Como essa pergunta menciona o VB.NET algumas vezes e está marcada com o VB.NET, mas todas as outras respostas aqui estão em C #, pensei em adicionar a sintaxe exata para um aplicativo VB.NET também.

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

Usei o REMmarcador de comentário em vez da aspas simples aqui, porque o Stack Overflow parecia lidar com a sintaxe destacando um pouco melhor REM.

JustSomeDude
fonte
-13

O que você está tentando deve funcionar de acordo com os documentos do MSDN para .Net 2.0. Você também pode tentar uma tentativa / captura diretamente no ponto de entrada do aplicativo de console.

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

E agora a sua captura vai lidar com qualquer coisa não capturada ( na thread principal ). Pode ser simples e até reiniciar onde estava, se você quiser, ou você pode simplesmente deixar o aplicativo morrer e registrar a exceção. Você adicionará um finalmente se quiser fazer alguma limpeza. Cada encadeamento exigirá seu próprio tratamento de exceção de alto nível semelhante ao principal.

Editado para esclarecer o ponto sobre os tópicos, como apontado por BlueMonkMN e mostrado em detalhes em sua resposta.

Rodney S. Foley
fonte
1
Infelizmente, as exceções ainda podem ser lançadas fora do bloco Main (). Este não é realmente um "pegar tudo", como você imagina. Veja a resposta de @Hans.
Mike Atlas
@ Mike Primeiro, eu disse que o jeito que ele está fazendo isso é correto, e que ele poderia tentar um try / catch no principal. Não sei por que você (ou outra pessoa) me deu uma votação quando eu concordava com Hans, apenas fornecendo outra resposta que eu não esperava receber um cheque. Isso não é realmente justo e, então, dizer que a alternativa está errada sem fornecer nenhuma prova de como uma exceção que pode ser capturada pelo processo AppDomain UnhandledException que uma tentativa / captura em Main não pode capturar. Acho rude dizer que algo está errado sem provar por que está errado, apenas dizer que é assim, não o faz.
Rodney S. Foley
5
Eu publiquei o exemplo que você está pedindo. Seja responsável e remova seus votos irrelevantes das respostas antigas de Mike, se não tiver. (Sem interesse pessoal, só não gosto de ver esse abuso do sistema.) #
BlueMonkMN
3
No entanto, você ainda joga o mesmo "jogo" que ele, apenas de uma maneira pior, porque é pura retaliação, não baseada na qualidade de uma resposta. Essa não é uma maneira de resolver o problema, apenas torná-lo pior. É especialmente ruim quando você retaliar contra alguém que até tinha uma preocupação legítima com sua resposta (como eu demonstrei).
BlueMonkMN
3
Ah, eu também acrescentaria que o voto negativo não se destina a pessoas que estão "sendo um completo idiota ou violando as regras", mas para julgar a qualidade de uma resposta. Parece-me que as respostas de baixa votação para "comentar" a pessoa que as fornece é um abuso muito maior do que as respostas de baixa votação baseadas no conteúdo da própria resposta, independentemente de esse voto ser preciso ou não. Não tome / faça isso tão pessoal.
BlueMonkMN