¿Captura excepciones a nivel mundial en una aplicación WPF?

242

Tenemos una aplicación WPF donde partes de ella pueden generar excepciones en tiempo de ejecución. Me gustaría detectar globalmente cualquier excepción no controlada y registrarla, pero de lo contrario continuar con la ejecución del programa como si nada sucediera (algo así como VB On Error Resume Next).

¿Es esto posible en C #? Y si es así, ¿dónde exactamente necesitaría poner el código de manejo de excepciones?

Actualmente no puedo ver ningún punto único en el que pueda ajustar un try/ catchalrededor y que atrape todas las excepciones que puedan ocurrir. Y aun así, habría dejado todo lo que se ha ejecutado debido a la captura. ¿O estoy pensando en direcciones horriblemente incorrectas aquí?

ETA: Porque muchas personas a continuación lo señalaron: la aplicación no es para controlar plantas de energía nuclear. Si se bloquea, no es un gran problema, pero las excepciones aleatorias que están relacionadas principalmente con la interfaz de usuario son una molestia en el contexto en el que se usarían. Hubo (y probablemente todavía hay) algunos de ellos, ya que utiliza una arquitectura de complemento y puede ser extendido por otros (también estudiantes en ese caso; por lo tanto, no hay desarrolladores experimentados que puedan escribir código completamente libre de errores).

En cuanto a las excepciones que quedan atrapadas: las registro en un archivo de registro, incluida la traza completa de la pila. Ese fue el objetivo de ese ejercicio. Solo para contrarrestar a esas personas que estaban tomando mi analogía con el OERN de VB demasiado literalmente.

Sé que ignorar ciegamente ciertas clases de errores es peligroso y podría corromper mi instancia de aplicación. Como se dijo antes, este programa no es de misión crítica para nadie. Nadie en su sano juicio apostaría por la supervivencia de la civilización humana. Es simplemente una pequeña herramienta para probar ciertos enfoques de diseño wrt. Ingeniería de software.

Para el uso inmediato de la aplicación, no hay muchas cosas que puedan suceder en una excepción:

  • Sin manejo de excepciones: diálogo de error y salida de la aplicación. El experimento tiene que repetirse, aunque probablemente con otro sujeto. No se han registrado errores, lo cual es lamentable.
  • Manejo genérico de excepciones: error benigno atrapado, sin daño. Este debería ser el caso común a juzgar por todos los errores que estábamos viendo durante el desarrollo. Ignorar este tipo de errores no debería tener consecuencias inmediatas; Las estructuras de datos centrales se prueban lo suficientemente bien como para sobrevivir fácilmente a esto.
  • Manejo de excepciones genéricas: error grave atrapado, posiblemente bloqueado en un momento posterior. Esto puede suceder raramente. Nunca lo hemos visto hasta ahora. El error se registra de todos modos y un bloqueo puede ser inevitable. Entonces, esto es conceptualmente similar al primer caso. Excepto que tenemos un seguimiento de pila. Y en la mayoría de los casos, el usuario ni siquiera lo notará.

En cuanto a los datos del experimento generados por el programa: un error grave en el peor de los casos solo provocaría que no se registraran datos. Los cambios sutiles que cambian el resultado del experimento muy ligeramente son bastante improbables. E incluso en ese caso, si los resultados parecen dudosos, se registra el error; aún se puede descartar ese punto de datos si es un valor atípico total.

Para resumir: Sí, me considero todavía al menos parcialmente cuerdo y no considero una rutina de manejo de excepciones global que deje el programa en ejecución necesariamente necesariamente malo. Como se dijo dos veces antes, tal decisión podría ser válida, dependiendo de la aplicación. En este caso se juzgó una decisión válida y no una mierda total y absoluta. Para cualquier otra aplicación, esa decisión podría verse diferente. Pero, por favor, no me acusen a mí ni a las otras personas que trabajaron en ese proyecto para hacer explotar el mundo potencialmente solo porque estamos ignorando errores.

Nota al margen: hay exactamente un usuario para esa aplicación. No es algo como Windows u Office que usan millones donde el costo de tener excepciones para el usuario sería muy diferente en primer lugar.

Joey
fuente
Esto realmente no está pensando: sí, probablemente desee que la aplicación salga. PERO, ¿no sería bueno registrar primero la excepción con su StackTrace? Si todo lo que obtuvo del usuario fue: "Su aplicación se bloqueó cuando presioné este botón", es posible que nunca pueda resolver el problema porque no tendría suficiente información. Pero si primero registró la excepción antes de cancelar la aplicación de manera más placentera, tendrá mucha más información.
Russ
Elaboré ese punto un poco en la pregunta ahora. Conozco los riesgos involucrados y para esa aplicación en particular se consideró aceptable. Y abortar la aplicación para algo tan simple como un índice fuera de límites mientras la interfaz de usuario intenta hacer una buena animación es excesivo e innecesario. Sí, no sé la causa exacta, pero tenemos datos para respaldar la afirmación de que la gran mayoría de los casos de error son benignos. Los graves que estamos enmascarando pueden hacer que la aplicación se bloquee, pero eso es lo que habría sucedido sin el manejo de excepciones globales de todos modos.
Joey el
Otra nota al margen: si evita cualquier bloqueo con este enfoque, lo más probable es que a los usuarios les encante.
lahjaton_j
Consulte el ejemplo de excepciones no controladas de manejo de Windows en WPF (la colección más completa de controladores) en C # para Visual Studio 2010 . Tiene 5 ejemplos que incluyen AppDomain.CurrentDomain.FirstChanceException, Application.DispatcherUnhandledException y AppDomain.CurrentDomain.UnhandledException.
user34660
Me gustaría agregar que el flujo de código similar a VB On Error Resume Nextno es posible en C #. Después de un Exception(C # no tiene "errores") no puede simplemente continuar con la siguiente instrucción: la ejecución continuará en un catchbloque, o en uno de los controladores de eventos que se describen en las respuestas a continuación.
Mike

Respuestas:

191

Usa el Application.DispatcherUnhandledException Event. Vea esta pregunta para un resumen (vea la respuesta de Drew Noakes ).

Tenga en cuenta que todavía habrá excepciones que impiden la reanudación exitosa de su aplicación, como después de un desbordamiento de pila, memoria agotada o pérdida de conectividad de red mientras intenta guardar en la base de datos.

David Schmitt
fuente
Gracias. Y sí, soy consciente de que hay excepciones de las que no me puedo recuperar, pero la gran mayoría de las que podrían ocurrir son benignas en este caso.
Joey el
14
Si su excepción ocurre en un hilo de fondo (digamos, usando ThreadPool.QueueUserWorkItem), esto no funcionará.
Szymon Rozga
@siz: buen punto, pero esos se pueden manejar con un bloque de prueba normal en el elemento de trabajo, ¿no?
David Schmitt
1
@PitiOngmongkolkul: el controlador se llama como evento desde su bucle principal. Cuando regresan los controladores de eventos, su aplicación continúa normalmente.
David Schmitt
44
Parece que necesitamos establecer e.Handled = true, donde e es un DispatcherUnhandledExceptionEventArgs, para omitir el controlador predeterminado que cierra los programas. msdn.microsoft.com/en-us/library/…
Piti Ongmongkolkul
55

Código de ejemplo que usa NLog que capturará excepciones lanzadas desde todos los subprocesos en el dominio de aplicación , desde el subproceso del despachador de la interfaz de usuario y desde las funciones asíncronas :

App.xaml.cs:

public partial class App : Application
{
    private static Logger _logger = LogManager.GetCurrentClassLogger();

    protected override void OnStartup(StartupEventArgs e)
    {
        base.OnStartup(e);

        SetupExceptionHandling();
    }

    private void SetupExceptionHandling()
    {
        AppDomain.CurrentDomain.UnhandledException += (s, e) =>
            LogUnhandledException((Exception)e.ExceptionObject, "AppDomain.CurrentDomain.UnhandledException");

        DispatcherUnhandledException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "Application.Current.DispatcherUnhandledException");
            e.Handled = true;
        };

        TaskScheduler.UnobservedTaskException += (s, e) =>
        {
            LogUnhandledException(e.Exception, "TaskScheduler.UnobservedTaskException");
            e.SetObserved();
        };
    }

    private void LogUnhandledException(Exception exception, string source)
    {
        string message = $"Unhandled exception ({source})";
        try
        {
            System.Reflection.AssemblyName assemblyName = System.Reflection.Assembly.GetExecutingAssembly().GetName();
            message = string.Format("Unhandled exception in {0} v{1}", assemblyName.Name, assemblyName.Version);
        }
        catch (Exception ex)
        {
            _logger.Error(ex, "Exception in LogUnhandledException");
        }
        finally
        {
            _logger.Error(exception, message);
        }
    }
Hüseyin Yağlı
fuente
10
¡Esta es la respuesta más completa aquí! Se incluyen las excepciones del planificador Taks. Lo mejor para mí, código limpio y simple.
Nikola Jovic
3
Recientemente tuve una corrupción de App.config en un cliente, y su aplicación ni siquiera estaba comenzando porque NLog estaba tratando de leer desde App.config y lanzó una excepción. Como esa excepción estaba en el inicializador del registrador estático , el UnhandledExceptioncontrolador no la estaba captando. Tuve que mirar el Visor de registro de eventos de Windows para encontrar lo que estaba sucediendo ...
heltonbiker
Recomiendo al conjunto e.Handled = true;en el UnhandledExceptionque la aplicación no se colgará en la interfaz de usuario Excepciones
Apfelkuacha
30

AppDomain.UnhandledException Event

Este evento proporciona notificación de excepciones no detectadas. Permite que la aplicación registre información sobre la excepción antes de que el controlador predeterminado del sistema informe la excepción al usuario y finalice la aplicación.

   public App()
   {
      AppDomain currentDomain = AppDomain.CurrentDomain;
      currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);    
   }

   static void MyHandler(object sender, UnhandledExceptionEventArgs args) 
   {
      Exception e = (Exception) args.ExceptionObject;
      Console.WriteLine("MyHandler caught : " + e.Message);
      Console.WriteLine("Runtime terminating: {0}", args.IsTerminating);
   }

Si el evento UnhandledException se maneja en el dominio de aplicación predeterminado, se genera allí para cualquier excepción no controlada en cualquier hilo, sin importar en qué dominio de aplicación se haya iniciado el hilo. Si el hilo se inició en un dominio de aplicación que tiene un controlador de eventos para UnhandledException, el evento se genera en ese dominio de aplicación. Si ese dominio de aplicación no es el dominio de aplicación predeterminado, y también hay un controlador de eventos en el dominio de aplicación predeterminado, el evento se genera en ambos dominios de aplicación.

Por ejemplo, suponga que un subproceso comienza en el dominio de aplicación "AD1", llama a un método en el dominio de aplicación "AD2" y desde allí llama a un método en el dominio de aplicación "AD3", donde arroja una excepción. El primer dominio de aplicación en el que se puede generar el evento UnhandledException es "AD1". Si ese dominio de aplicación no es el dominio de aplicación predeterminado, el evento también se puede generar en el dominio de aplicación predeterminado.

CharithJ
fuente
copiando de la URL a la que apuntó (que habla de aplicaciones de consola y aplicaciones de WinForms solo creo): "Comenzando con .NET Framework 4, este evento no se genera para excepciones que corrompen el estado del proceso, como desbordamientos de pila o infracciones de acceso, a menos que el controlador de eventos sea crítico para la seguridad y tenga el atributo HandleProcessCorruptedStateExceptionsAttribute ".
George Birbilis
1
@GeorgeBirbilis: puede suscribirse al evento UnhandledException en el constructor de la aplicación.
CharithJ
18

Además de lo que otros mencionaron aquí, tenga en cuenta que la combinación de Application.DispatcherUnhandledException(y sus similares ) con

<configuration>
  <runtime>  
    <legacyUnhandledExceptionPolicy enabled="1" />
  </runtime>
</configuration>

en el app.configevitará que la excepción de subprocesos secundarios cierre la aplicación.

Hertzel Guinness
fuente
2

Aquí hay un ejemplo completo usando NLog

using NLog;
using System;
using System.Windows;

namespace MyApp
{
    /// <summary>
    /// Interaction logic for App.xaml
    /// </summary>
    public partial class App : Application
    {
        private static Logger logger = LogManager.GetCurrentClassLogger();

        public App()
        {
            var currentDomain = AppDomain.CurrentDomain;
            currentDomain.UnhandledException += CurrentDomain_UnhandledException;
        }

        private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
        {
            var ex = (Exception)e.ExceptionObject;
            logger.Error("UnhandledException caught : " + ex.Message);
            logger.Error("UnhandledException StackTrace : " + ex.StackTrace);
            logger.Fatal("Runtime terminating: {0}", e.IsTerminating);
        }        
    }


}
Desarrollador
fuente
-4

Como "¿VB's en error reanudar después?" Eso suena un poco aterrador. La primera recomendación es no hacerlo. La segunda recomendación es no hacerlo y no pensar en ello. Necesita aislar mejor sus fallas. En cuanto a cómo abordar este problema, depende de cómo esté estructurado su código. Si está utilizando un patrón como MVC o similar, entonces esto no debería ser demasiado difícil y definitivamente no requeriría una deglución de excepción global. En segundo lugar, busque una buena biblioteca de registro como log4net o use el rastreo. Necesitaríamos conocer más detalles, como qué tipo de excepciones está hablando y qué partes de su aplicación pueden generar excepciones.

BobbyShaftoe
fuente
2
El punto es que quiero mantener la aplicación ejecutándose incluso después de excepciones no detectadas. Solo quiero registrarlos (tenemos un marco de registro personalizado para esto, según nuestras necesidades) y no necesito abortar todo el programa solo porque algún complemento hizo algo extraño en su código de interacción con el usuario. Por lo que he visto hasta ahora, no es trivial aislar las causas limpiamente, ya que las diferentes partes no se conocen entre sí.
Joey el
99
Entiendo, pero debe tener mucho cuidado de continuar después de una excepción que no examina, existe la posibilidad de corrupción de datos, etc.
BobbyShaftoe
3
Estoy de acuerdo con BobbyShaftoe. Este no es el camino correcto. Deje que la aplicación se bloquee. Si la falla está en algún complemento, entonces arregle o haga que alguien arregle el complemento. Dejarlo funcionando es EXTREMADAMENTE peligroso. Obtendrá efectos secundarios extraños y llegará a un punto en el que no podrá encontrar una explicación lógica de los errores que ocurren en su aplicación.
1
Elaboré ese punto un poco en la pregunta ahora. Conozco los riesgos involucrados y para esa aplicación en particular se consideró aceptable. Y abortar la aplicación para algo tan simple como un índice fuera de límites mientras la interfaz de usuario intenta hacer una buena animación es excesivo e innecesario. Sí, no sé la causa exacta, pero tenemos datos para respaldar la afirmación de que la gran mayoría de los casos de error son benignos. Los graves que estamos enmascarando pueden hacer que la aplicación se bloquee, pero eso es lo que habría sucedido sin el manejo de excepciones globales de todos modos.
Joey el
Esto debería ser un comentario, no una respuesta. Responder a "cómo hago esto" con "probablemente no deberías" no es una respuesta, es un comentario.
Josh Noe