c# - run - Controlador de excepción global TAP
task c# example (5)
Bueno, ¿cómo definirías un manejador global de aplicaciones para lidiar con una excepción en este caso?
string x = DoSomething();
Es probable que la respuesta a su pregunta sea exactamente la misma. Parece que está esperando correctamente un método asíncrono, y el compilador hace todo lo posible para asegurarse de que cualquier excepción que se produzca en el método asincrónico se propague y desenrolle de una manera que le permita manejarlo como lo haría en el código síncrono. Este es uno de los principales beneficios de async / await.
Este código arroja una excepción. ¿Es posible definir un controlador global de aplicación que lo atrape?
string x = await DoSomethingAsync();
Usando .net 4.5 / WPF
EDITADO según el comentario de @Noseration
En .NET 4.5 en código async
, puede manejar excepciones no observadas registrando un controlador para el evento TaskScheduler.UnobservedTaskException
. Se considera que una excepción no se observa si no se accede a las propiedades Task.Result
, Task.Exception
y no se llama a Task.Wait
.
Después de que la excepción no observada llega al controlador de eventos TaskScheduler.UnobservedTaskException
, el comportamiento predeterminado es tragar esta excepción para que el programa no falle. Este comportamiento se puede cambiar en el archivo de configuración agregando lo siguiente:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
Esta es realmente una buena pregunta, si lo entendí correctamente. Inicialmente voté para cerrarlo, pero ahora se retractó de mi voto.
Es importante comprender cómo una excepción arrojada dentro de un método de async Task
se propaga fuera de él. Lo más importante es que dicha excepción debe ser observada por el código que maneja la finalización de la tarea.
Por ejemplo, aquí hay una aplicación WPF simple, estoy en NET 4.5.1:
using System;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication_22369179
{
public partial class MainWindow : Window
{
Task _task;
public MainWindow()
{
InitializeComponent();
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
_task = DoAsync();
}
async Task DoAsync()
{
await Task.Delay(1000);
MessageBox.Show("Before throwing...");
GCAsync(); // fire-and-forget the GC
throw new ApplicationException("Surprise");
}
async void GCAsync()
{
await Task.Delay(1000);
MessageBox.Show("Before GC...");
// garbage-collect the task without observing its exception
_task = null;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
}
void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
MessageBox.Show("TaskScheduler_UnobservedTaskException:" + e.Exception.Message);
}
void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
MessageBox.Show("CurrentDomain_UnhandledException:" + ((Exception)e.ExceptionObject).Message);
}
}
}
Una vez que se ha lanzado ApplicationException
, no se observa. Ni TaskScheduler_UnobservedTaskException
ni CurrentDomain_UnhandledException
se invocan. La excepción permanece inactiva hasta que el objeto _task
sea esperado o esperado. En el ejemplo anterior, nunca se observa, por TaskScheduler_UnobservedTaskException
que se invoca a TaskScheduler_UnobservedTaskException
solo cuando la tarea se recolecta . Entonces esta excepción será tragada .
El antiguo comportamiento .NET 4.0, donde el evento AppDomain.CurrentDomain.UnhandledException
se activa y la aplicación falla, se puede habilitar configurando ThrowUnobservedTaskExceptions
en app.config
:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
Cuando se habilita de esta manera, AppDomain.CurrentDomain.UnhandledException
seguirá TaskScheduler.UnobservedTaskException
después de TaskScheduler.UnobservedTaskException
cuando la excepción se recolecta basura, en lugar de en el lugar donde se lanzó.
Este comportamiento lo describe Stephen Toub en su publicación de blog "Task Exception Handling in .NET 4.5" . La parte sobre la recolección de basura de tareas se describe en los comentarios a la publicación.
Ese es el caso con los métodos de async Task
. La historia es bastante diferente para los métodos async void
, que normalmente se usan para manejadores de eventos. Cambiemos el código de esta manera:
public MainWindow()
{
InitializeComponent();
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
this.Loaded += MainWindow_Loaded;
}
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
MessageBox.Show("Before throwing...");
throw new ApplicationException("Surprise");
}
Debido a que es un async void
no hay referencia de Task
a la que aferrarse (por lo que no se observará nada o se recolectará basura más adelante). En este caso, la excepción se lanza inmediatamente en el contexto de sincronización actual. Para una aplicación WPF, Dispatcher.UnhandledException
activará primero, luego Application.Current.DispatcherUnhandledException
, luego AppDomain.CurrentDomain.UnhandledException
. Finalmente, si no se maneja ninguno de estos eventos ( EventArgs.Handled
no se establece en true
), la aplicación se bloqueará, independientemente de la configuración ThrowUnobservedTaskExceptions
. TaskScheduler.UnobservedTaskException
no se TaskScheduler.UnobservedTaskException
en este caso, por el mismo motivo: no hay Task
.
Al vincular un evento a AppDomain.CurrentDomain.FirstChanceException
se garantiza que su excepción se detectará. Como señaló @Noseratio, se le notificará cada excepción en su aplicación, incluso si la excepción se maneja correctamente dentro de un bloque catch y la aplicación continúa.
Sin embargo, todavía veo que este evento es útil para al menos capturar las últimas excepciones lanzadas antes de que se detenga una aplicación o quizás algún otro escenario de depuración.
Si quieres protegerte contra esto
string x = await DoSomethingAsync();
Mi consejo es que no hagas eso, agrega un bloque try catch :-)
Siempre puede hacer lo siguiente para manejar la excepción utilizando el método Application.DispatcherUnhandledException
. Por supuesto, se te daría dentro de una TargetInvocationException
y podría no ser tan bonita como otros métodos. Pero funciona perfectamente bien
_executeTask = executeMethod(parameter);
_executeTask.ContinueWith(x =>
{
Dispatcher.CurrentDispatcher.Invoke(new Action<Task>((task) =>
{
if (task.Exception != null)
throw task.Exception.Flatten().InnerException;
}), x);
}, TaskContinuationOptions.OnlyOnFaulted);