c# - Fire-and-forget con async vs "viejo delegado asíncrono"
asynchronous c#-5.0 (4)
Como se señala en las otras respuestas, y por esta excelente jaylee.org/post/2012/07/08/… , 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):
Crea un método de extensión para
Task
. Ejecuta laTask
pasada y captura / registra cualquier excepción:static async void FireAndForget(this Task task) { try { await task; } catch (Exception e) { // log errors } }
Siempre use los métodos
async
estilo deTask
al crearlos, nunca comoasync void
.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.
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 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);
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;
}
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