Espera un método asíncrono nulo

155

¿Cómo puedo esperar a que un void asyncmétodo termine su trabajo?

Por ejemplo, tengo una función como la siguiente:

async void LoadBlahBlah()
{
    await blah();
    ...
}

ahora quiero asegurarme de que todo se haya cargado antes de continuar en otro lugar.

MBZ
fuente

Respuestas:

234

La mejor práctica es marcar la función async voidsolo si es el método de disparo y olvido, si desea esperar, debe marcarlo como async Task.

En caso de que aún quieras esperar, envuélvelo así await Task.Run(() => blah())

Rohit Sharma
fuente
obviamente, bla está devolviendo la tarea y tiene una firma asincrónica por qué lo llamarías sin esperar. incluso VS te avisará al respecto.
batmaci
8
await Task.Run(() => An_async_void_method_I_can_not_modify_now())
themefield
1
await Task.Run(() => blah())es engañosa. Esto no espera la finalización de la función asíncrona blah, solo espera la creación (trivial) de la tarea y continúa inmediatamente antes de que se blah()haya completado.
Jonathan Lidbeck
@JonathanLidbeck la declaración "continúa inmediatamente antes de que bla esté mal. Intenta" esperar Task.Run (() => Thread.Sleep (10_000)) ", la tarea se espera durante 10 segundos o más antes de ejecutar cualquier línea siguiente
Rohit Sharma
@RohitSharma, eso es correcto para su ejemplo, pero Thread.Sleepno es asíncrono. Esta pregunta se trata de esperar una async voidfunción, por ejemploasync void blah() { Task.Delay(10000); }
Jonathan Lidbeck
59

Si puede cambiar la firma de su función a, async Taskentonces puede usar el código presentado aquí

Comunidad
fuente
27

La mejor solución es usar async Task. Debe evitar async voidpor varias razones, una de las cuales es la componibilidad.

Si no se puede hacer que el método regrese Task(p. Ej., Es un controlador de eventos), puede usar SemaphoreSlimpara que la señal del método esté a punto de salir. Considera hacer esto en un finallybloque.

Stephen Cleary
fuente
1

haga un AutoResetEvent, llame a la función, luego espere en AutoResetEvent y luego configúrelo dentro de vacío asíncrono cuando sepa que está hecho.

También puede esperar una Tarea que regrese de su vacío asíncrono

usuario2712345
fuente
1

Realmente no necesita hacer nada manualmente, la awaitpalabra clave detiene la ejecución de la función hasta que blah()regrese.

private async void SomeFunction()
{
     var x = await LoadBlahBlah(); <- Function is not paused
     //rest of the code get's executed even if LoadBlahBlah() is still executing
}

private async Task<T> LoadBlahBlah()
{
     await DoStuff();  <- function is paused
     await DoMoreStuff();
}

Tes el tipo de objeto blah()devuelve

Realmente no puede awaituna voidfunción, así LoadBlahBlah()que no puede servoid

Mayank
fuente
Quiero esperar a LoadBlahBlah()que termine, noblah()
MBZ
10
Desafortunadamente, los métodos de vacío asíncrono no pausan la ejecución (a diferencia de los métodos de tareas asíncronas)
ghord
-1

Sé que esta es una vieja pregunta, pero aún sigo siendo un problema, y ​​todavía no hay una solución clara para hacerlo correctamente cuando se usa async / wait en un método de firma vacío async.

Sin embargo, noté que .Wait () funciona correctamente dentro del método void.

y como async void y void tienen la misma firma, es posible que deba hacer lo siguiente.

void LoadBlahBlah()
{
    blah().Wait(); //this blocks
}

Lo suficientemente confuso como async / await no se bloquea en el siguiente código.

async void LoadBlahBlah()
{
    await blah(); //this does not block
}

Cuando descompila su código, supongo que el vacío asíncrono crea una Tarea interna (al igual que la Tarea asíncrona), pero dado que la firma no admite devolver esas Tareas internas

Esto significa que internamente el método vacío asíncrono aún podrá "esperar" los métodos asíncronos internos. pero externamente no puede saber cuándo se completa la tarea interna.

Entonces, mi conclusión es que el vacío asíncrono funciona según lo previsto, y si necesita comentarios de la Tarea interna, entonces debe usar la firma de la Tarea asíncrona en su lugar.

espero que mis divagaciones tengan sentido para cualquiera que también busque respuestas.

Editar: hice un código de ejemplo y lo descompuse para ver qué está pasando realmente.

static async void Test()
{
    await Task.Delay(5000);
}

static async Task TestAsync()
{
    await Task.Delay(5000);
}

Se convierte en (editar: sé que el código del cuerpo no está aquí sino en las máquinas de estado, pero las máquinas de estado eran básicamente idénticas, así que no me molesté en agregarlas)

private static void Test()
{
    <Test>d__1 stateMachine = new <Test>d__1();
    stateMachine.<>t__builder = AsyncVoidMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncVoidMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
}
private static Task TestAsync()
{
    <TestAsync>d__2 stateMachine = new <TestAsync>d__2();
    stateMachine.<>t__builder = AsyncTaskMethodBuilder.Create();
    stateMachine.<>1__state = -1;
    AsyncTaskMethodBuilder <>t__builder = stateMachine.<>t__builder;
    <>t__builder.Start(ref stateMachine);
    return stateMachine.<>t__builder.Task;
}

ni AsyncVoidMethodBuilder ni AsyncTaskMethodBuilder tienen realmente ningún código en el método Start que les sugiera que bloqueen y siempre se ejecuten de forma asíncrona después de que se inicien.

lo que significa que sin la Tarea de retorno, no habría forma de verificar si está completa.

como se esperaba, solo inicia la Tarea ejecutándose de forma asíncrona y luego continúa en el código. y la Tarea asíncrona, primero comienza la Tarea y luego la devuelve.

así que supongo que mi respuesta sería nunca usar el vacío asíncrono, si necesita saber cuándo se realiza la tarea, para eso está la tarea asíncrona.

Droa
fuente