Disparar y olvidar con async vs "antiguo delegado async"

Resuelto mmix asked hace 12 años • 5 respuestas

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 EndInvokey 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/ awaitde manera de disparar y olvidar sin obtener una solución que sea más complicada que la "forma antigua"?

mmix avatar Oct 09 '12 22:10 mmix
Aceptado

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);
Stephen Cleary avatar Oct 09 '2012 16:10 Stephen Cleary

Como se indica en las otras respuestas, y en esta excelente publicación de blog , desea evitar el uso async voidde 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):

  1. Cree un método de extensión para Task. Ejecuta lo pasado Tasky detecta/registra cualquier excepción:

    static async void FireAndForget(this Task task)
    {
       try
       {
            await task;
       }
       catch (Exception e)
       {
           // log errors
       }
    }
    
  2. Utilice siempre métodos Taskde estilo asyncal crearlos, nunca async void.

  3. Invoque esos métodos de esta manera:

    MyTaskAsyncMethod().FireAndForget();
    

No es necesario await(ni generará la awaitadvertencia). También manejará cualquier error correctamente y, como este es el único lugar donde colocas async void, no tienes que acordarte de colocar try/catchbloques en todas partes.

Esto también le da la opción de no utilizar el asyncmétodo como método de "disparar y olvidar" si realmente lo desea awaitnormalmente.

BradleyDotNET avatar Jan 09 '2015 01:01 BradleyDotNET

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.

Daniel C. Weber avatar Oct 09 '2012 15:10 Daniel C. Weber