Disparar y olvidar con async vs "antiguo delegado async"
Estoy tratando de reemplazar mis antiguas llamadas de disparar y olvidar con una nueva sintaxis, esperando una mayor simplicidad y parece que se me escapa. Aquí hay un ejemplo
class Program
{
static void DoIt(string entry)
{
Console.WriteLine("Message: " + entry);
}
static async void DoIt2(string entry)
{
await Task.Yield();
Console.WriteLine("Message2: " + entry);
}
static void Main(string[] args)
{
// old way
Action<string> async = DoIt;
async.BeginInvoke("Test", ar => { async.EndInvoke(ar); ar.AsyncWaitHandle.Close(); }, null);
Console.WriteLine("old-way main thread invoker finished");
// new way
DoIt2("Test2");
Console.WriteLine("new-way main thread invoker finished");
Console.ReadLine();
}
}
Ambos enfoques hacen lo mismo, sin embargo, lo que parece haber ganado (no es necesario hacerlo EndInvoke
y cerrar el control, lo cual en mi humilde opinión todavía es un poco discutible) lo estoy perdiendo en la nueva forma al tener que esperar unTask.Yield()
, lo que en realidad plantea un nuevo problema de tener que reescribir todos los métodos asincrónicos de F&F existentes solo para agregar esa frase. ¿Existen algunas ganancias invisibles en términos de rendimiento/limpieza?
¿Cómo haría para aplicar async si no puedo modificar el método en segundo plano? Me parece que no hay una forma directa, ¿tendría que crear un método asíncrono contenedor que esperaría a Task.Run()?
Editar: ahora veo que es posible que me falten preguntas reales. La pregunta es: dado un método sincrónico A(), ¿cómo puedo llamarlo de forma asincrónica usando async
/ await
de manera de disparar y olvidar sin obtener una solución que sea más complicada que la "forma antigua"?
Evitar async void
. Tiene una semántica complicada en torno al manejo de errores; Sé que algunas personas lo llaman "disparar y olvidar", pero yo suelo utilizar la frase "disparar y estrellar".
La pregunta es: dado un método sincrónico A(), ¿cómo puedo llamarlo de forma asincrónica usando async/await de manera de disparar y olvidar sin obtener una solución que sea más complicada que la "forma antigua"?
No necesitas async
/ await
. Simplemente llámalo así:
Task.Run(A);
Como se indica en las otras respuestas, y en esta excelente publicación de blog , desea evitar el uso async void
de controladores de eventos externos a la interfaz de usuario. Si desea un método seguro de "disparar y olvidar" async
, considere usar este patrón (crédito a @ReedCopsey; este método es uno que me dio en una conversación de chat):
Cree un método de extensión para
Task
. Ejecuta lo pasadoTask
y detecta/registra cualquier excepción:static async void FireAndForget(this Task task) { try { await task; } catch (Exception e) { // log errors } }
Utilice siempre métodos
Task
de estiloasync
al crearlos, nuncaasync void
.Invoque esos métodos de esta manera:
MyTaskAsyncMethod().FireAndForget();
No es necesario await
(ni generará la await
advertencia). También manejará cualquier error correctamente y, como este es el único lugar donde colocas async void
, no tienes que acordarte de colocar try/catch
bloques en todas partes.
Esto también le da la opción de no utilizar el async
método como método de "disparar y olvidar" si realmente lo desea await
normalmente.
A mí me parece que "esperar" algo y "disparar y olvidar" son dos conceptos ortogonales. O inicias un método de forma asincrónica y no te importa el resultado, o deseas reanudar la ejecución en el contexto original una vez finalizada la operación (y posiblemente usar un valor de retorno), que es exactamente lo que hace await. Si solo desea ejecutar un método en un subproceso ThreadPool (para que su interfaz de usuario no se bloquee), opte por
Task.Factory.StartNew(() => DoIt2("Test2"))
y estarás bien.