.NET controlador de excepción global en la aplicación de consola

198

Pregunta: Quiero definir un controlador de excepciones global para excepciones no controladas en mi aplicación de consola. En asp.net, uno puede definir uno en global.asax, y en aplicaciones / servicios de Windows, uno puede definir lo siguiente

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

Pero, ¿cómo puedo definir un controlador de excepción global para una aplicación de consola?
currentDomain parece no funcionar (.NET 2.0)?

Editar:

Argh, estúpido error.
En VB.NET, uno necesita agregar la palabra clave "AddHandler" delante de currentDomain, o de lo contrario no se ve el evento UnhandledException en IntelliSense ...
Eso es porque los compiladores de VB.NET y C # tratan el manejo de eventos de manera diferente.

Stefan Steiger
fuente

Respuestas:

283

No, esa es la forma correcta de hacerlo. Esto funcionó exactamente como debería, algo de lo que puede trabajar quizás:

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

Tenga en cuenta que no puede detectar las excepciones de tipo y carga de archivos generadas por el jitter de esta manera. Suceden antes de que su método Main () comience a ejecutarse. Capturarlos requiere retrasar la fluctuación de fase, mover el código riesgoso a otro método y aplicar el atributo [MethodImpl (MethodImplOptions.NoInlining)].

Hans Passant
fuente
3
Implementé lo que propusiste aquí, pero no quiero salir de la aplicación. Solo quiero registrarlo y continuar el proceso (sin Console.ReadLine()ninguna otra perturbación del flujo del programa. Pero lo que obtengo es la excepción que vuelve a aparecer una y otra vez, y otra vez.
3
@Shahrooz Jefri: No puedes continuar una vez que obtienes una excepción no controlada. La pila está en mal estado, y esto es terminal. Si tiene un servidor, lo que puede hacer en UnhandledExceptionTrapper es reiniciar el programa con los mismos argumentos de línea de comando.
Stefan Steiger
66
¡Ciertamente lo hace! No estamos hablando del evento Application.ThreadException aquí.
Hans Passant
44
Creo que la clave para entender esta respuesta y los comentarios en respuesta es darse cuenta de que este código demuestra ahora detectar la excepción, pero no "manejarla" completamente como lo haría en un bloque de prueba / captura normal donde tiene la opción de continuar . Vea mi otra respuesta para más detalles. Si "maneja" la excepción de esta manera, no tiene opción de continuar ejecutándose cuando ocurre una excepción en otro hilo. En ese sentido, se puede decir que esta respuesta no "maneja" por completo la excepción si con "manejar" quiere decir "procesar y continuar ejecutándose".
BlueMonkMN
44
Sin mencionar que hay muchas tecnologías para ejecutar en diferentes hilos. Por ejemplo, la Biblioteca Paralela de Tareas (TPL) tiene su propia forma de detectar excepciones no controladas. Entonces, decir que esto no funciona para todas las situaciones es un poco ridículo, NO hay un solo lugar para todo en C #, pero dependiendo de las tecnologías que use, hay varios lugares en los que puede conectarse.
Doug
23

Si tiene una aplicación de un solo subproceso, puede usar un simple try / catch en la función Principal, sin embargo, esto no cubre las excepciones que se pueden lanzar fuera de la función Principal, en otros hilos, por ejemplo (como se señaló en otro comentarios) Este código demuestra cómo una excepción puede hacer que la aplicación finalice a pesar de que trataste de manejarla en Main (observa cómo el programa se cierra correctamente si presionas Intro y permites que la aplicación salga correctamente antes de que ocurra la excepción, pero si dejas que se ejecute , termina bastante infelizmente):

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

Puede recibir una notificación de cuándo otro subproceso arroja una excepción para realizar una limpieza antes de que la aplicación salga, pero por lo que puedo decir, desde una aplicación de consola no puede forzar la aplicación a continuar ejecutándose si no maneja la excepción en el hilo desde el que se lanza sin usar algunas opciones de compatibilidad oscuras para que la aplicación se comporte como lo haría con .NET 1.x. Este código demuestra cómo se puede notificar al subproceso principal de las excepciones que provienen de otros subprocesos, pero aún así terminará 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;
   }
}

Entonces, en mi opinión, la forma más limpia de manejarlo en una aplicación de consola es asegurarse de que cada hilo tenga un controlador de excepción en el nivel raíz:

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
fuente
TRY CATCH no funciona en modo de lanzamiento para errores inesperados: /
Muflix
12

También necesita manejar excepciones de hilos:

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

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

Whoop, perdón por las formas win, por cualquier hilo que estés usando en una aplicación de consola tendrás que encerrarlo en un bloque try / catch. Los subprocesos en segundo plano que encuentran excepciones no controladas no hacen que la aplicación finalice.

BlackICE
fuente
1

Acabo de heredar una vieja aplicación de consola VB.NET y necesitaba configurar un controlador de excepción global. Como esta pregunta menciona VB.NET varias veces y está etiquetada con VB.NET, pero todas las demás respuestas aquí están en C #, pensé que también agregaría la sintaxis exacta para una aplicación 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

Usé el REMmarcador de comentarios en lugar de la comilla simple aquí porque Stack Overflow parecía manejar un poco mejor el resaltado de sintaxis REM.

JustSomeDude
fuente
-13

Lo que está intentando debería funcionar de acuerdo con los documentos de MSDN para .Net 2.0. También puede intentar un try / catch justo en main alrededor de su punto de entrada para la aplicación de consola.

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

Y ahora su captura manejará todo lo que no haya sido capturado ( en el hilo principal ). Puede ser elegante e incluso reiniciar donde estaba si lo desea, o simplemente puede dejar que la aplicación muera y registrar la excepción. Debería agregar un finalmente si desea hacer alguna limpieza. Cada subproceso requerirá su propio manejo de excepciones de alto nivel similar al principal.

Editado para aclarar el punto sobre los hilos como lo señala BlueMonkMN y se muestra en detalle en su respuesta.

Rodney S. Foley
fuente
1
Desafortunadamente, todavía se pueden lanzar excepciones fuera del bloque Main (). Esto no es realmente un "atrapar todo" como podría pensar. Ver la respuesta de @Hans.
Mike Atlas
@ Mike Primero dije que la forma en que lo está haciendo es correcta, y que podría intentar un try / catch en general. No estoy seguro de por qué usted (u otra persona) me dio un voto negativo cuando estaba de acuerdo con Hans simplemente proporcionando otra respuesta para la que no esperaba obtener un cheque. Eso no es realmente justo, y luego decir que la alternativa está mal sin proporcionar ninguna prueba de cómo una excepción que puede ser detectada por el proceso AppDomain UnhandledException que un intento / captura en Main no puede detectar. Me parece grosero decir que algo está mal sin probar por qué está mal, solo decir que es así, no lo hace así.
Rodney S. Foley
55
He publicado el ejemplo que estás pidiendo. Sea responsable y elimine sus votos irrelevantes de las viejas respuestas de Mike si no lo ha hecho. (Sin interés personal, simplemente no me gusta ver tal abuso del sistema).
BlueMonkMN
3
Sin embargo, todavía juegas el mismo "juego" que él, solo que de una manera peor porque es pura represalia, no basada en la calidad de una respuesta. Esa no es una forma de resolver el problema, solo empeorarlo. Es especialmente malo cuando tomas represalias contra alguien que incluso tenía una preocupación legítima sobre tu respuesta (como lo he demostrado).
BlueMonkMN
3
Ah, también agregaría que el voto negativo no está destinado a personas que están "siendo un completo idiota o violando las reglas", sino más bien para juzgar la calidad de una respuesta. Me parece que las respuestas con voto negativo para "comentar" sobre la persona que las proporciona es un abuso mucho mayor que las respuestas con voto negativo basadas en el contenido de la respuesta en sí misma, independientemente de si ese voto es correcto o no. No lo tomes / lo hagas tan personal.
BlueMonkMN