c# - generate - Enfoque de fuego y olvido
params comments c# (3)
Relacionado con esta respuesta ,
Si realmente quiero "disparar y olvidar" un método que devuelve una tarea, y (por simplicidad) supongamos que no se espera que el método arroje ninguna excepción. Puedo usar el método de extensión que figura en la respuesta:
public static void Forget(this Task task)
{
}
Usando este enfoque, si hay errores en la acción de la Task
que hacen que se emita una excepción, cuando se lanza la excepción inesperada, la excepción será eliminada y pasará desapercibida.
Pregunta: ¿No sería más apropiado en este escenario que el método de extensión fuera de la forma siguiente?
public static async void Forget(this Task task)
{
await task;
}
Para que los errores de programación arrojen una excepción y se escalen (por lo general, reduciendo el proceso).
En el caso de un método con excepciones esperadas (e ignorables), el método necesitaría ser más elaborado (como un aparte, cualquier sugerencia sobre cómo construir una versión de este método que tomaría una lista de tipos de excepción aceptables e ignorables? )
Depende de la semántica que quieras. Si desea asegurarse de que se noten las excepciones, entonces sí, puede await
la tarea. Pero en ese caso, no es realmente "fuego y olvidar".
Un verdadero "fuego y olvídate", en el sentido de que no te importa cuándo se completa o si se completa con éxito o con error, es extremadamente raro.
Editar:
Para el manejo de excepciones:
public static async void Forget(this Task task, params Type[] acceptableExceptions)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception ex)
{
// TODO: consider whether derived types are also acceptable.
if (!acceptableExceptions.Contains(ex.GetType()))
throw;
}
}
Tenga en cuenta que recomiendo usar await
lugar de ContinueWith
. ContinueWith
un planificador predeterminado sorprendente (como se indica en mi blog) y Task.Exception
ajustará la excepción real en una AggregateException
, haciendo que el código de manejo de errores sea más engorroso.
En la pregunta vinculada , inicialmente quise utilizar static void Forget(this Task task)
en el siguiente contexto:
var task = DoWorkAsync();
QueueAsync(task).Forget();
// ...
async Task QueueAsync(Task task)
{
// keep failed/cancelled tasks in the list
// they will be observed outside
_pendingTasks.Add(task);
await task;
_pendingTasks.Remove(tasks)
}
Se veía muy bien, pero luego me di cuenta de que las excepciones fatales posiblemente lanzadas por _pendingTasks.Add
/ _pendingTasks.Remove
se verían ni se perderían, lo cual no es bueno.
Así que simplemente hice QueueTask
un método de async void
, lo que esencialmente es:
var task = DoWorkAsync();
QueueAsync(task);
// ...
async void QueueAsync(Task task)
{
// keep failed/cancelled tasks in the list
// they will be observed outside
_pendingTasks.Add(task);
try
{
await task;
}
catch
{
return;
}
_pendingTasks.Remove(tasks)
}
Por mucho que no me guste la catch {}
vacía catch {}
, creo que tiene sentido aquí.
En este escenario, mantener la async Task QueueAsync()
y usar async void Forget(this Task task)
como propones sería en overkill, IMO.
Sí, si te interesaba si la tarea arrojaba una excepción o no, tendrías que await
el resultado, pero por otro lado, eso derrota el propósito de "disparar y olvidar ".
En el escenario en el que desea saber si algo malo sucedió, la forma recomendada de hacerlo es usar una continuación, por ejemplo:
public static void ForgetOrThrow(this Task task)
{
task.ContinueWith((t) => {
Console.WriteLine(t.Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
}