¿Cómo esperar a que se complete el método asíncrono?

138

Estoy escribiendo una aplicación WinForms que transfiere datos a un dispositivo de clase USB HID. Mi aplicación utiliza la excelente biblioteca genérica HID v6.0 que se puede encontrar aquí . En pocas palabras, cuando necesito escribir datos en el dispositivo, este es el código que se llama:

private async void RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        RequestToGetInputReport();
    }
}

Cuando mi código cae del ciclo while, necesito leer algunos datos del dispositivo. Sin embargo, el dispositivo no puede responder de inmediato, por lo que debo esperar a que vuelva esta llamada antes de continuar. Como existe actualmente, RequestToGetInputReport () se declara así:

private async void RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}

Para lo que vale, la declaración para GetInputReportViaInterruptTransfer () se ve así:

internal async Task<int> GetInputReportViaInterruptTransfer()

Desafortunadamente, no estoy muy familiarizado con el funcionamiento de las nuevas tecnologías async / await en .NET 4.5. Leí un poco antes sobre la palabra clave de espera y eso me dio la impresión de que la llamada a GetInputReportViaInterruptTransfer () dentro de RequestToGetInputReport () esperaría (¿y tal vez sí?) Pero no parece la llamada a RequestToGetInputReport () en sí está esperando porque parece que estoy volviendo a entrar en el ciclo while casi de inmediato?

¿Alguien puede aclarar el comportamiento que estoy viendo?

bmt22033
fuente

Respuestas:

131

Evitar async void. Haga que sus métodos regresen en Tasklugar de void. Entonces puedes awaitellos.

Me gusta esto:

private async Task RequestToSendOutputReport(List<byte[]> byteArrays)
{
    foreach (byte[] b in byteArrays)
    {
        while (condition)
        {
            // we'll typically execute this code many times until the condition is no longer met
            Task t = SendOutputReportViaInterruptTransfer();
            await t;
        }

        // read some data from device; we need to wait for this to return
        await RequestToGetInputReport();
    }
}

private async Task RequestToGetInputReport()
{
    // lots of code prior to this
    int bytesRead = await GetInputReportViaInterruptTransfer();
}
Stephen Cleary
fuente
1
Muy bonito, gracias. Me estaba rascando la cabeza por un problema similar y la diferencia era cambiar voida Tasklo que había dicho.
Jeremy
8
Es algo menor, pero para seguir la convención, ambos métodos deben tener Async agregado a sus nombres, por ejemplo, RequestToGetInputReportAsync ()
tymtam
66
¿Y si la persona que llama es la función principal?
simbionte
14
@symbiont: Entonces useGetAwaiter().GetResult()
Stephen Cleary
44
@AhmedSalah El Taskrepresenta la ejecución del método, por lo returnque se colocan valores y se colocan Task.Resultexcepciones Task.Exception. Con void, el compilador no tiene ningún lugar para colocar excepciones, por lo que simplemente se vuelven a generar en un subproceso de grupo de subprocesos.
Stephen Cleary
229

Lo más importante que debe saber asyncy awaites que await no espera a que se complete la llamada asociada. Lo que awaithace es devolver el resultado de la operación de forma inmediata y sincrónica si la operación ya se ha completado o, si no lo ha hecho, programar una continuación para ejecutar el resto del asyncmétodo y luego devolver el control a la persona que llama. Cuando finalice la operación asincrónica, se ejecutará la finalización programada.

La respuesta a la pregunta específica en el título de su pregunta es bloquear el asyncvalor de retorno de un método (que debe ser de tipo Tasko Task<T>) llamando a un Waitmétodo apropiado :

public static async Task<Foo> GetFooAsync()
{
    // Start asynchronous operation(s) and return associated task.
    ...
}

public static Foo CallGetFooAsyncAndWaitOnResult()
{
    var task = GetFooAsync();
    task.Wait(); // Blocks current thread until GetFooAsync task completes
                 // For pedagogical use only: in general, don't do this!
    var result = task.Result;
    return result;
}

En este fragmento de código, CallGetFooAsyncAndWaitOnResultes un contenedor sincrónico alrededor del método asincrónico GetFooAsync. Sin embargo, este patrón debe evitarse en su mayor parte, ya que bloqueará un subproceso de grupo de subprocesos completo durante la operación asincrónica. Este es un uso ineficiente de los diversos mecanismos asincrónicos expuestos por las API que hacen grandes esfuerzos para proporcionarlos.

La respuesta en "esperar" no espera a que se complete la llamada, tiene varias explicaciones más detalladas de estas palabras clave.

Mientras tanto, la orientación de @Stephen Cleary sobre las async voidretenciones. Otras buenas explicaciones de por qué se pueden encontrar en http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ y https://jaylee.org/archive/ 2012/07/08 / c-sharp-async-tips-and-tricks-part-2-async-void.html

Richard Cook
fuente
18
Me resulta útil pensar (y hablar) awaitcomo una "espera asincrónica", es decir, bloquea el método (si es necesario) pero no el hilo . Por lo tanto, tiene sentido hablar sobre RequestToSendOutputReport"esperar" RequestToGetInputReportaunque no sea una espera de bloqueo .
Stephen Cleary
@ Richard Cook: ¡muchas gracias por la explicación adicional!
bmt22033
10
Esta debería ser la respuesta aceptada, ya que responde más claramente a la pregunta real (es decir, cómo bloquear un hilo en un método asíncrono).
csvan
la mejor solución es esperar asíncrono hasta que la tarea completada sea var result = Task.Run (async () => {return waitit yourMethod ();}). Result;
Ram chittala
70

La mejor solución para esperar AsynMethod hasta completar la tarea es

var result = Task.Run(async() => await yourAsyncMethod()).Result;
Ram chittala
fuente
15
O esto para su "vacío" asíncrono: Task.Run (async () => {wait yourAsyncMethod ();}). Wait ();
Jiří Herník
1
¿Cuál es el beneficio de esto sobre yourAsyncMethod (). Resultado?
Justin J Stark
1
Simplemente acceder a la propiedad .Result en realidad no espera hasta que la tarea termine de ejecutarse. De hecho, creo que arroja una excepción si se llama antes de completar una tarea. Creo que la ventaja de incluir esto en una llamada Task.Run () es que, como Richard Cook menciona a continuación, "esperar" en realidad no espera a que se complete una tarea, pero el uso de una llamada .Wait () bloquea todo su grupo de subprocesos . Esto le permite ejecutar (sincrónicamente) un método asíncrono en un hilo separado. Ligeramente confuso, pero ahí está.
Lucas Leblanc
un buen resultado, justo lo que necesitaba
Gerry
Recordatorio rápido de la función ECMA7 como assync () o espera no funcionará en un entorno anterior a ECMA7.
Mbotet
0

Aquí hay una solución alternativa usando una bandera:

//outside your event or method, but inside your class
private bool IsExecuted = false;

private async Task MethodA()
{

//Do Stuff Here

IsExecuted = true;
}

.
.
.

//Inside your event or method

{
await MethodA();

while (!isExecuted) Thread.Sleep(200); // <-------

await MethodB();
}
lemi impactante
fuente
-1

simplemente ponga Wait () para esperar hasta que se complete la tarea

GetInputReportViaInterruptTransfer().Wait();

Firas Nizam
fuente
Esto bloquea el hilo actual. Por lo tanto, esto suele ser algo malo.
Pure.Krome
-4

En realidad, esto me pareció más útil para las funciones que devuelven IAsyncAction.

            var task = asyncFunction();
            while (task.Status == AsyncStatus.Completed) ;
Barış Tanyeri
fuente
-5

El siguiente fragmento muestra una forma de garantizar que se complete el método esperado antes de volver a la persona que llama. SIN EMBARGO, no diría que es una buena práctica. Edite mi respuesta con explicaciones si piensa lo contrario.

public async Task AnAsyncMethodThatCompletes()
{
    await SomeAsyncMethod();
    DoSomeMoreStuff();
    await Task.Factory.StartNew(() => { }); // <-- This line here, at the end
}

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
Jerther
fuente
Votantes, ¿te gustaría explicar, como pregunté en la respuesta? Porque esto funciona bien hasta donde yo sé ...
Jerther
3
El problema es que la única forma en que puedes hacer el await+ the Console.WriteLinees convirtiéndose en a Task, lo que cede el control entre los dos. por lo que su 'solución' finalmente generará una Task<T>, que no resuelve el problema. Hacer un Task.Waittestamento en realidad detendrá el procesamiento (con posibilidades de punto muerto, etc.). En otras palabras, en awaitrealidad no espera, simplemente combina dos partes ejecutables asincrónicamente en una sola Task(que alguien puede mirar o esperar)
Ruben Bartelink