Pregunta Fire-and-forget con async vs "viejo delegado asíncrono"


Estoy tratando de reemplazar mis antiguas llamadas a "disparar y olvidar" con una nueva sintaxis, esperando más simplicidad y parece eludirme. 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 EndInvoke y el manejo cercano, que todavía es un poco discutible) Estoy perdiendo de la nueva manera al tener que esperar un Task.Yield(), que en realidad plantea un nuevo problema de tener que volver a escribir todos los métodos asincrónicos de F & F para agregar ese único liner. ¿Hay algunas ganancias invisibles en términos de rendimiento / limpieza?

¿Cómo voy a aplicar la aplicación asincrónica si no puedo modificar el método de fondo? Me parece que no hay una manera directa, tendría que crear un método asíncrono envoltorio que esperaría Task.Run ()?

Editar: ahora veo que me pueden estar perdiendo algunas preguntas reales. La pregunta es: dado un método sincrónico A (), ¿cómo puedo llamarlo de forma asincrónica? async/await en una manera de olvidar y olvidar sin obtener una solución que es más complicada que la "vieja usanza"


44
2017-10-09 15:04


origen


Respuestas:


Evitar async void. Tiene una semántica complicada en cuanto al manejo de errores; Sé que algunas personas lo llaman "fuego y olvídate", pero suelo usar la frase "fuego y colisión".

La pregunta es: dado un método síncrono A (), ¿cómo puedo llamarlo asincrónicamente usando async / await de una manera de olvidar y olvidar sin obtener una solución que es más complicada que la "vieja manera"

No necesitas async / await. Solo llámalo así:

Task.Run(A);

62
2017-10-09 16:06



Como se señaló en las otras respuestas, y por este excelente entrada en el blog quieres evitar el uso async void fuera de los controladores de eventos de UI. Si quieres un seguro "dispara y olvida" async método, considere usar este patrón (crédito a @ReedCopsey; este método es el que me dio en una conversación de chat):

  1. Crea un método de extensión para Task. Corre el pasado Task y captura / registra cualquier excepción:

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

  3. Invoca esos métodos de esta manera:

    MyTaskAsyncMethod().FireAndForget();
    

No necesitas await (ni generará el await advertencia). También manejará cualquier error correctamente, y ya que este es el único lugar donde has puesto async void, no tienes que recordar poner try/catch bloquea en todas partes.

Esto también te da la opción de no utilizando el async método como método de "fuego y olvídate" si realmente quieres await normalmente


30
2018-01-09 01:19



Para mí, parece que "esperar" algo y "disparar y olvidar" son dos conceptos ortogonales. O bien inicia un método de forma asíncrona y no le importa el resultado, o si desea reanudar la ejecución en el contexto original una vez que la operación ha finalizado (y posiblemente use un valor de retorno), que es exactamente lo que espera. Si solo desea ejecutar un método en un hilo de ThreadPool (para que su UI no se bloquee), vaya a

Task.Factory.StartNew(() => DoIt2("Test2"))

y estarás bien


15
2017-10-09 15:26



Mi sensación es que estos métodos de "fuego y olvido" fueron en gran parte artefactos de la necesidad de una forma limpia de intercalar la IU y el código de fondo para que pueda escribir su lógica como una serie de instrucciones secuenciales. Como async / await se encarga de organizar el SynchronizationContext, esto se convierte en un problema menor. El código en línea en una secuencia más larga se convierte efectivamente en tus bloques "disparar y olvidar" que previamente se habrían lanzado desde una rutina en un hilo de fondo. Es efectivamente una inversión del patrón.

La principal diferencia es que los bloques entre espera son más similares a Invoke que BeginInvoke. Si necesita un comportamiento más parecido al de BeginInvoke, puede llamar al siguiente método asíncrono (devolviendo una Tarea), y luego, en realidad, no esperará la Tarea devuelta hasta después del código que quería 'BeginInvoke'.

    public async void Method()
    {
        //Do UI stuff
        await SomeTaskAsync();
        //Do more UI stuff (as if called via Invoke from a thread)
        var nextTask = NextTaskAsync();
        //Do UI stuff while task is running (as if called via BeginInvoke from a thread)
        await nextTask;
    }

0
2017-10-09 15:16