Aguarde um método nulo assíncrono

155

Como posso esperar que um void asyncmétodo termine seu trabalho?

por exemplo, eu tenho uma função como abaixo:

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

agora quero ter certeza de que tudo foi carregado antes de continuar em outro lugar.

MBZ
fonte

Respostas:

234

A melhor prática é marcar a função async voidapenas se for o método de ignorar e esquecer; se você quiser aguardar, deve marcá-la como async Task.

Caso ainda deseje aguardar, envolva-o da seguinte maneira await Task.Run(() => blah())

Rohit Sharma
fonte
Obviamente, blá está retornando tarefas e tem uma assinatura assíncrona. Por que você a chamaria sem esperar? até o VS avisa sobre isso.
batmaci 27/02/19
8
await Task.Run(() => An_async_void_method_I_can_not_modify_now())
themefield 12/03/19
1
await Task.Run(() => blah())é enganoso. Isso não espera a conclusão da função assíncrona blah, apenas a criação (trivial) da tarefa e continua imediatamente antes da blah()conclusão.
Jonathan Lidbeck 17/04
@JonathanLidbeck a declaração "continua imediatamente antes que blá esteja errado. Tente" aguardar Task.Run (() => Thread.Sleep (10_000)) ", a tarefa é aguardada por 10 segundos ou mais antes de executar qualquer próxima linha
Rohit Sharma em
@RohitSharma, isso está correto para o seu exemplo, mas Thread.Sleepnão é assíncrono. Esta questão é sobre aguardar uma async voidfunção, digamosasync void blah() { Task.Delay(10000); }
Jonathan Lidbeck
59

Se você pode alterar a assinatura de sua função para, async Taskentão você pode usar o código apresentado aqui

Comunidade
fonte
27

A melhor solução é usar async Task. Você deve evitar async voidpor vários motivos, um dos quais é a composição.

Se o método não puder ser retornado Task(por exemplo, é um manipulador de eventos), você poderá usar SemaphoreSlimo sinal do método quando ele estiver prestes a sair. Considere fazer isso em um finallybloco.

Stephen Cleary
fonte
1

faça um AutoResetEvent, chame a função e aguarde AutoResetEvent e defina-o dentro de async void quando você souber que está pronto.

Você também pode aguardar uma tarefa que retorne do seu vazio assíncrono

user2712345
fonte
1

Você realmente não precisa fazer nada manualmente, a awaitpalavra-chave pausa a execução da função até blah()retornar.

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

Té o tipo de objeto blah()retornado

Você não pode realmente awaituma voidfunção, então LoadBlahBlah()não pode servoid

Mayank
fonte
Eu quero esperar para LoadBlahBlah()terminar, nãoblah()
MBZ
10
Infelizmente assíncrona métodos vazios não pausar a execução (ao contrário assíncrona métodos de tarefas)
Ghord
-1

Eu sei que essa é uma pergunta antiga, mas ainda é um problema que eu continuo abordando, e ainda não há uma solução clara para fazer isso corretamente ao usar async / waitit em um método de assinatura assíncrona de void.

No entanto, notei que .Wait () está funcionando corretamente dentro do método void.

e como async void e void têm a mesma assinatura, você pode precisar fazer o seguinte.

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

Confundidamente, async / waitit não é bloqueado no próximo código.

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

Quando você descompila seu código, meu palpite é que o async void cria uma tarefa interna (assim como a tarefa assíncrona), mas como a assinatura não suporta retornar essas tarefas internas

isso significa que internamente o método de async void ainda poderá "aguardar" os métodos de async internamente. mas externamente incapaz de saber quando a tarefa interna está concluída.

Portanto, minha conclusão é que o async void está funcionando conforme o esperado e, se você precisar de feedback da tarefa interna, precisará usar a assinatura da tarefa assíncrona.

espero que minhas divagações façam sentido para quem também procura respostas.

Edit: Eu criei um código de exemplo e o descompilei para ver o que realmente está acontecendo.

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

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

Se transforma em (editar: eu sei que o código do corpo não está aqui, mas nas máquinas de estado, mas as máquinas de estado eram basicamente idênticas, então eu não me incomodei em adicioná-las)

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

nem o AsyncVoidMethodBuilder nem o AsyncTaskMethodBuilder têm realmente qualquer código no método Start que indique a sua obstrução e sempre será executado de forma assíncrona após o início.

significando sem a tarefa retornada, não haveria maneira de verificar se ela está completa.

conforme o esperado, ele inicia apenas a tarefa executando assíncrona e continua no código. e a tarefa assíncrona, primeiro ela inicia a tarefa e depois a devolve.

então acho que minha resposta seria nunca usar o async void, se você precisar saber quando a tarefa está concluída, é para isso que serve a tarefa assíncrona.

Droa
fonte