Enfoque de fuego y olvido

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 progtwigció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.

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); } 

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.