Como aguardar a conclusão do método assíncrono?

138

Estou escrevendo um aplicativo WinForms que transfere dados para um dispositivo de classe USB HID. Meu aplicativo usa a excelente biblioteca HID genérica v6.0, que pode ser encontrada aqui . Em poucas palavras, quando preciso gravar dados no dispositivo, este é o código que é chamado:

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

Quando meu código sai do loop while, preciso ler alguns dados do dispositivo. No entanto, o dispositivo não pode responder imediatamente, por isso preciso aguardar o retorno dessa ligação antes de continuar. Como existe atualmente, RequestToGetInputReport () é declarado assim:

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

Quanto vale a pena, a declaração para GetInputReportViaInterruptTransfer () é semelhante a esta:

internal async Task<int> GetInputReportViaInterruptTransfer()

Infelizmente, não estou muito familiarizado com o funcionamento das novas tecnologias async / wait no .NET 4.5. Eu fiz uma pequena leitura anterior sobre a palavra-chave wait e isso me deu a impressão de que a chamada para GetInputReportViaInterruptTransfer () dentro de RequestToGetInputReport () esperaria (e talvez espere?), Mas não parece ser a chamada para RequestToGetInputReport () em si está esperando porque parece que eu estou entrando novamente no loop while quase imediatamente?

Alguém pode esclarecer o comportamento que estou vendo?

bmt22033
fonte

Respostas:

131

Evite async void. Faça com que seus métodos retornem em Taskvez de void. Então você pode awaiteles.

Como isso:

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
fonte
1
Muito bom obrigado. Eu estava coçando minha cabeça em uma questão semelhante e a diferença era mudar voidpara Taskexatamente como você havia dito.
Jeremy
8
É uma coisa menor, mas para seguir a convenção de ambos os métodos devem ter Async adicionado aos seus nomes, por exemplo RequestToGetInputReportAsync ()
tymtam
6
e se o chamador for a função principal?
586
14
@symbiont: Então useGetAwaiter().GetResult()
Stephen Cleary
4
@AhmedSalah O Taskrepresenta a execução do método - portanto, os returnvalores são colocados Task.Resulte as exceções são colocadas Task.Exception. Com void, o compilador não tem onde colocar exceções, portanto elas são apenas aumentadas em um thread do conjunto de threads.
Stephen Cleary
229

A coisa mais importante a saber asynce awaité que await não espera a conclusão da chamada associada. O que awaitfaz é retornar o resultado da operação imediatamente e de forma síncrona se a operação já tiver sido concluída ou, se não tiver, agendar uma continuação para executar o restante do asyncmétodo e, em seguida, retornar o controle ao chamador. Quando a operação assíncrona for concluída, a conclusão agendada será executada.

A resposta para a pergunta específica no título da sua pergunta é bloquear asynco valor de retorno de um método (que deve ser do tipo Taskou Task<T>) chamando um Waitmétodo apropriado :

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

Nesse trecho de código, CallGetFooAsyncAndWaitOnResulté um invólucro síncrono em torno do método assíncrono GetFooAsync. No entanto, esse padrão deve ser evitado na maior parte do tempo, pois ele bloqueará um encadeamento de conjunto de encadeamentos inteiro durante a operação assíncrona. Esse é um uso ineficiente dos vários mecanismos assíncronos expostos pelas APIs que envidam grandes esforços para fornecê-los.

A resposta em "aguardar" não espera a conclusão da chamada tem várias explicações mais detalhadas dessas palavras-chave.

Enquanto isso, a orientação de Stephen Cleary sobre async voidretenções. Outras explicações interessantes sobre o porquê podem ser encontradas em http://www.tonicodes.net/blog/why-you-should-almost-never-write-void-asynchronous-methods/ e https://jaylee.org/archive/ 2012/07/08 / c-sharp-async-dicas-e-truques-parte-2-async-void.html

Richard Cook
fonte
18
Acho útil pensar (e falar) awaitcomo uma "espera assíncrona" - ou seja, bloqueia o método (se necessário), mas não o encadeamento . Portanto, faz sentido falar em RequestToSendOutputReport"esperar", RequestToGetInputReportmesmo que não seja uma espera bloqueadora .
Stephen Cleary
@ Richard Cook - muito obrigado pela explicação adicional!
precisa saber é o seguinte
10
Essa deve ser a resposta aceita, uma vez que responde mais claramente à pergunta real (isto é, como bloquear um thread em um método assíncrono).
Csvan
a melhor solução é aguardar async até que a tarefa seja concluída var var = Task.Run (async () => {return waitit yourMethod ();}). Result;
Ram chittala
70

A melhor solução para esperar o AsynMethod até concluir a tarefa é

var result = Task.Run(async() => await yourAsyncMethod()).Result;
Ram chittala
fonte
15
Ou isso para o seu async "void": Task.Run (async () => {aguarde yourAsyncMethod ();}). Wait ();
Jiří Herník
1
Qual é o benefício disso em relação ao yourAsyncMethod (). Result?
Justin J Stark
1
Simplesmente acessar a propriedade .Result, na verdade, não espera até que a tarefa seja concluída. Na verdade, acredito que isso gera uma exceção se for chamado antes que uma tarefa seja concluída. Eu acho que a vantagem de agrupar isso em uma chamada Task.Run () é que, como Richard Cook menciona abaixo, "aguardar", na verdade, não espera uma tarefa ser concluída, mas o uso de uma chamada .Wait () bloqueia todo o conjunto de threads . Isso permite que você execute (de forma síncrona) um método assíncrono em um thread separado. Um pouco confuso, mas aí está.
Lucas Leblanc
bom arremesso no resultado lá, apenas o que eu precisava
Gerry
Lembrete rápido O ECMA7 assine semelhante a um recurso () ou aguarda não funcionará no ambiente anterior ao ECMA7.
Mbotet 11/07/19
0

Aqui está uma solução alternativa usando um sinalizador:

//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 chocante
fonte
-1

basta colocar Wait () para aguardar até que a tarefa seja concluída

GetInputReportViaInterruptTransfer().Wait();

Firas Nizam
fonte
Isso bloqueia o segmento atual. Portanto, isso geralmente é uma coisa ruim a se fazer.
Pure.Krome 19/01
-4

Na verdade, achei isso mais útil para funções que retornam IAsyncAction.

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

O fragmento a seguir mostra uma maneira de garantir que o método esperado seja concluído antes de retornar ao chamador. No entanto, eu não diria que é uma boa prática. Por favor, edite minha resposta com explicações, se você pensa o contrário.

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

await AnAsyncMethodThatCompletes();
Console.WriteLine("AnAsyncMethodThatCompletes() completed.")
Jerther
fonte
Downvoters, gostaria de explicar, como eu pedi na resposta? Porque isso funciona bem, tanto quanto eu sei ...
Jerther
3
O problema é que a única maneira de você fazer o await+ Console.WriteLineé se tornando um Task, o que abre mão do controle entre os dois. portanto, sua 'solução' acabará gerando um Task<T>, que não soluciona o problema. Realizar um Task.Waittestamento realmente interromperá o processamento (com possibilidades de conflito etc.). Em outras palavras, awaitna verdade não esperar, ele simplesmente combina duas porções de forma assíncrona executáveis em um único Task(que alguém pode assistir ou esperar)
Ruben Bartelink