Fire-and-forget con async vs “viejo delegado asíncrono”

Estoy tratando de reemplazar mis antiguas llamadas a “disparar y olvidar” con una nueva syntax, 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 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 close handle, que todavía es un poco debatible) estoy perdiendo de la nueva manera al tener que esperar un Task.Yield() , que en realidad plantea un nuevo problema de tener que reescribir todos los métodos asincrónicos de F & F solo para agregar ese trazador. ¿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 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”

Evita el 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 necesita async / await . Solo llámalo así:

 Task.Run(A); 

Como se señala en las otras respuestas, y por esta excelente publicación de blog , desea evitar el uso de un async void fuera de los controladores de eventos de IU. Si desea un método async seguro de “disparar y olvidar”, 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 . Ejecuta la Task pasada y captura / registra cualquier excepción:

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

  3. Invoca esos métodos de esta manera:

     MyTaskAsyncMethod().FireAndForget(); 

No es necesario que lo await (ni generará la alerta de await ). También manejará cualquier error de forma correcta , y como este es el único lugar en el que async void , no tienes que recordar poner bloques de try/catch todas partes.

Esto también le da la opción de no usar el método async como método de “disparar y olvidar” si realmente desea await normalmente.

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

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