c# - operadores - no se puede usar await en void
¿Dónde una tarea asíncrona arroja una excepción si no está esperando? (3)
Tengo el siguiente ejemplo: (por favor, también lea los comentarios en el código, ya que tendrá más sentido)
public async Task<Task<Result>> MyAsyncMethod()
{
Task<Result> resultTask = await _mySender.PostAsync();
return resultTask;
// in real-life case this returns to a different assembly which I can''t change
// but I need to do some exception handling on the Result in here
}
supongamos que el método PostAsync
de _ mySender
ve así:
public Task<Task<Result>> PostAsync()
{
Task<Result> result = GetSomeTask();
return result;
}
La pregunta es:
Como no espero el Result
real en el MyAsyncMethod
y si el método PostAsync
arroja una excepción, ¿en qué contexto se va a lanzar y manejar la excepción?
y
¿Hay alguna forma en que pueda manejar excepciones en mi ensamblado?
Me sorprendió que cuando traté de cambiar MyAsyncMethod
a:
public async Task<Task<Result>> MyAsyncMethod()
{
try
{
Task<Result> resultTask = await _mySender.PostAsync();
return resultTask;
}
catch (MyCustomException ex)
{
}
}
la excepción fue capturada aquí, evento si no hay aguardar el resultado real. Sucede que el resultado de PostAsync
ya está disponible y la excepción se produce en este contexto ¿verdad?
¿Es posible usar ContinueWith
para manejar excepciones en la clase actual? Por ejemplo:
public async Task<Task<Result>> MyAsyncMethod()
{
Task<Result> resultTask = await _mySender.PostAsync();
var exceptionHandlingTask = resultTask.ContinueWith(t => { handle(t.Exception)}, TaskContinuationOptions.OnlyOnFaulted);
return resultTask;
}
Como no espero el resultado real en el método MyAsyncMethod y si el método PostAsync arroja una excepción, ¿en qué contexto se va a lanzar y manejar la excepción?
Si no await
ninguna de las tareas en su código o no asigna una continuación, el comportamiento puede variar según la versión de .NET Framework que esté utilizando. En ambos casos, la Task
devuelta tragará la excepción, la diferencia ocurrirá en la finalización:
.NET 4.0 - El hilo del finalizador volverá a lanzar la excepción tragada. Si no se registra ningún manejador de excepción global, terminará el proceso
.NET 4.5 y superior: la excepción será eliminada y pasará desapercibida.
En ambos casos se TaskScheduler.UnobservedTaskException
evento TaskScheduler.UnobservedTaskException
:
Se produce cuando una excepción no observada de una tarea en falla está a punto de desencadenar una política de escalación de excepciones, que, de forma predeterminada, daría por terminado el proceso.
Cuando un método de devolución de tareas no async
ejecuta de forma síncrona, la excepción se propociona inmediatamente y es por eso que está capturando la excepción sin usar await
, pero definitivamente no debería estar dependiendo de eso en su código.
¿Hay alguna forma en que pueda manejar excepciones en mi ensamblaje?
Sí tu puedes. Te aconsejaría que await
tareas que ejecutas dentro de tu conjunto.
No hay ninguna razón para usar el modificador async
si no estás esperando nada:
public Task<Result> PostAsync()
{
return GetSomeTask();
}
Luego, puede await
en PostAsync
y atrapar la excepción allí:
public async Task<Result> MyAsyncMethod()
{
try
{
// No need to use Task<Result> as await will unwrap the outter task
return await _mySender.PostAsync();
}
catch (MyCustomException e)
{
// handle here
}
}
Incluso puede modificar este código aún más y eliminar la palabra clave async
y posiblemente atrapar la excepción incluso más arriba de la pila de llamadas al método que llama a MyAsyncMethod
.
Esta es una gran cantidad de preguntas para empacar en una sola "pregunta", pero está bien ...
¿Dónde una tarea asíncrona arroja una excepción si no está esperando?
El evento TaskScheduler.UnobservedTaskException genera excepciones de tareas no TaskScheduler.UnobservedTaskException
. Este evento se plantea "eventualmente" porque la Tarea en realidad debe ser recogida de basura antes de que su excepción se considere no controlada.
Como no espero el resultado real en el método MyAsyncMethod y si el método PostAsync arroja una excepción, ¿en qué contexto se va a lanzar y manejar la excepción?
Cualquier método que use el modificador async
y devuelva una Task
pondrá todas sus excepciones en esa Task
devuelta.
¿Hay alguna forma en que pueda manejar excepciones en mi ensamblado?
Sí, podría reemplazar la tarea devuelta, algo así como:
async Task<Result> HandleExceptionsAsync(Task<Result> original)
{
try
{
return await original;
}
catch ...
}
public async Task<Task<Result>> MyAsyncMethod()
{
Task<Result> resultTask = await _mySender.PostAsync();
return HandleExceptionsAsync(resultTask);
}
Me sorprendió que cuando traté de cambiar MyAsyncMethod para [regresar de forma síncrona la tarea interna] la excepción fue capturada aquí, incluso si no hay aguardar el resultado real.
Eso en realidad significa que el método que está llamando no es una async Task
, como lo muestra su ejemplo de código. Es un método de recuperación de Task
no async
, y cuando uno de esos métodos arroja una excepción, se trata como cualquier otra excepción (es decir, pasa directamente por la pila de llamadas, no se coloca en la Task
devuelta).
¿Es posible usar ContinueWith para manejar excepciones en la clase actual?
Sí, pero await
es más limpio.
Utilizo un método de extensión para el manejo genérico de errores en la Task
. Esto proporciona una forma de registrar todos los errores y realizar una acción personalizada si se produce un error.
public static async void ErrorHandle(this Task task, Action action = null)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception e)
{
Log.Error(e);
if (action != null)
action();
}
}
Tiendo a usarlo cuando hago una Task
"fuego y olvídate":
Task.Run(() => ProcessData(token), token).ErrorHandle(OnError);