WPF: Atrapar errores a nivel de aplicación

Introducción

Los buenos desarrolladores de software son conscientes de la necesidad de atrapar errores dentro de una aplicación, para tener control sobre ellas y para evitar «sorpresas» en una aplicación puesta en producción. Sin embargo, existen excepciones que escapan de dichos controles por encontrarse fuera del contexto en el cual nos encontramos en un determinado momento de la ejecución de la aplicación.

Contexto

Dado que en una aplicación podemos ejecutar varios hilos, ejecutar tareas y trabajos en segundo plano existen disponibles maneras de atrapar errores generales dentro de una aplicación WPF. Particularmente utilizo estas 4:

1) Application.DispatcherUnhandled Exception

Este evento es lanzado cuando una excepción no contrada ocurre en la aplicación, excepción de cualquier índole que no haya sido capturada mediante el bloque try…catch en cualquier parte del código fuente de la aplicación. De manera predeterminada este evento atrapa todas las excepciones producidas en la aplicación, salvo los casos posteriores donde trabajamos con Tareas o servicios en segundo plano.

2) AppDomain.UnhandledException

Este evento se dispara cuando la excepción no ha podido ser atrapada por el evento de arriba (DispatcherUnhandledException) debido a que existen varias instancias de la misma aplicación. Este evento se dispara raras veces porque afectan a aplicaciones ejecutándose en simultáneo (varias instancias de la misma aplicación).

3) Application.Current.Dispatcher .UnhandledException

Este evento atrapa  excepciones que ocurren en la aplicación pero específicamente para métodos delegados o directamente por Hilos (Thread) que corresponden a trabajos en segundo plano. Cuando dichas excepciones ocurren en la ejecución de otro hilo (Thread) se lanzará este evento para atrapar la excepción correspondiente.

4) TaskScheduler.UnobservedTaskException

Este evento se lanza cuando ocurre una excepción justo antes de terminar la ejecución de la tarea, este evento permite que la tarea termine para que no quede realizando la tarea pendiente, pues ha ocurrido una excepción y la ejecución no puede seguir.

Utilizando el código

En el constructor de la clase debemos asignar los eventos para poder utilizarlos posteriormente. Lo hacemos de la siguiente manera:

public void App()
 {
     this.DispatcherUnhandledException += App_DispatcherUnhandledException;     

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

     Application.Current.Dispatcher.UnhandledException += Dispatcher_UnhandledException;

     TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

     //Do some stuff here
 }

Posteriormente, debemos implementar el código necesario para atrapar los errores genéricos que pueden ocurrir en la aplicación en los distintos escenarios explicados anteriormente. Para que funcionen los manejadores de excepciones debemos implementar los eventos de la siguiente manera:

private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
   this.ShowError(e.Exception);
   //Prevent default unhandled exception processing
   e.Handled = true;
}

private void Dispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
    this.ShowError(e.Exception);

    // Prevent default unhandled exception processing
    e.Handled = true;
}

private void Exception_AppDomain(object sender, UnhandledExceptionEventArgs e)
{
    this.ShowError(e.ExceptionObject as Exception);
}

private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
    this.ShowError(e.Exception);
}

Y para concluir con el ejemplo, creamos un método privado que permita mostrar el mensaje correspondiente (este paso es opcional, en caso que no queramos mostrar ningún mensaje). La definición del método es:

private void ShowError(Exception ex)
{
    if (ex != null)
    {
        //Show error in a message
        MessageBox.Show(ex.Message);
    }
}

Conclusión

Atrapando las excepciones genéricas con los eventos mencionados arriba logramos que nuestra aplicación sea «Responsiva» y no de lugar a muchas excepciones. Claro está que algunas excepciones no tendrán mucho significado para los usuario, pero no siempre es necesario mostrar un cuadro de mensaje. Simplemente agregando estos eventos al constructor de la aplicación podremos evitar que dicha aplicación se cierre sin previo aviso ni quede registrado en el Visor de eventos del Windows.