Não entendo bem a diferença entre Task.Wait
e await
.
Eu tenho algo semelhante às seguintes funções em um serviço ASP.NET WebAPI:
public class TestController : ApiController
{
public static async Task<string> Foo()
{
await Task.Delay(1).ConfigureAwait(false);
return "";
}
public async static Task<string> Bar()
{
return await Foo();
}
public async static Task<string> Ros()
{
return await Bar();
}
// GET api/test
public IEnumerable<string> Get()
{
Task.WaitAll(Enumerable.Range(0, 10).Select(x => Ros()).ToArray());
return new string[] { "value1", "value2" }; // This will never execute
}
}
Onde Get
será um impasse.
O que poderia causar isso? Por que isso não causa um problema quando eu uso uma espera de bloqueio em vez de await Task.Delay
?
Task.Delay(1).Wait()
que é bom o suficiente.Task.Delay(1).Wait()
é basicamente a mesma coisa queThread.Sleep(1000)
. No código de produção real, raramente é apropriado.WaitAll
está causando o impasse. Veja o link para o meu blog na minha resposta para mais detalhes. Você deve usar em seuawait Task.WhenAll
lugar.ConfigureAwait(false)
uma chamada únicaBar
ouRos
não entra em conflito, mas porque possui um enumerável que está criando mais de uma e aguardando todas, a primeira barra entra em impasse na segunda. Se você, emawait Task.WhenAll
vez de esperar em todas as tarefas, para não bloquear o contexto ASP, verá o método retornar normalmente..ConfigureAwait(false)
todo o caminho até a árvore até que você bloquear, dessa forma nada está sempre tentando voltar para o contexto principal; Isso funcionaria. Outra opção seria ativar um contexto de sincronização interno. Link . Se você colocar o itemTask.WhenAll
em,AsyncPump.Run
ele efetivamente bloqueará a coisa toda sem a necessidade de ir aConfigureAwait
qualquer lugar, mas provavelmente é uma solução excessivamente complexa.Respostas:
Wait
eawait
- embora parecidos conceitualmente - são realmente completamente diferentes.Wait
será bloqueado de forma síncrona até que a tarefa seja concluída. Portanto, o encadeamento atual é literalmente bloqueado, aguardando a conclusão da tarefa. Como regra geral, você deve usar "async
todo o caminho"; isto é, não bloqueie oasync
código. No meu blog, mostro os detalhes de como o bloqueio de código assíncrono causa um conflito .await
aguardará assincronamente até que a tarefa seja concluída. Isso significa que o método atual está "pausado" (seu estado é capturado) e o método retorna uma tarefa incompleta para o responsável pela chamada. Posteriormente, quando aawait
expressão for concluída, o restante do método será agendado como uma continuação.Você também mencionou um "bloco cooperativo", pelo qual suponho que você quer dizer que uma tarefa na qual você está
Wait
executando pode ser executada no thread em espera. Há situações em que isso pode acontecer, mas é uma otimização. Há muitas situações em que isso não pode acontecer, como se a tarefa é para outro agendador, ou se já foi iniciada ou se é uma tarefa que não é de código (como no seu exemplo de código:Wait
não é possível executar aDelay
tarefa em linha porque não há código por isso).Você pode achar minha
async
/await
introdução útil.fonte
Wait
funciona bemawait
impasses.Wait
bloqueia o encadeamento e não pode ser usado para outras coisas.await
código. Ou isso, ou o impasse não estava relacionado a nenhum deles e você diagnosticou mal o problema.SynchronizationContext
, portanto, o bloqueio em uma solicitação do ASP.NET Core não causa mais conflitos.Com base no que li de diferentes fontes:
Uma
await
expressão não bloqueia o encadeamento no qual está executando. Em vez disso, faz com que o compilador inscreva o restante doasync
método como uma continuação da tarefa esperada. O controle retorna ao chamador doasync
método. Quando a tarefa é concluída, ela invoca sua continuação e a execução doasync
método continua de onde parou.Para aguardar
task
a conclusão de um único , você pode chamar seuTask.Wait
método. Uma chamada para oWait
método bloqueia o encadeamento de chamada até que a instância de classe única conclua a execução. OWait()
método sem parâmetros é usado para esperar incondicionalmente até que uma tarefa seja concluída. A tarefa simula o trabalho chamando oThread.Sleep
método para dormir por dois segundos.Este artigo também é uma boa leitura.
fonte
Alguns fatos importantes não foram dados em outras respostas:
O "async waiting" é mais complexo no nível CIL e, portanto, custa tempo de memória e CPU.
Qualquer tarefa pode ser cancelada se o tempo de espera for inaceitável.
No caso de "async aguardar", não temos um manipulador para essa tarefa cancelá-la ou monitorá-la.
O uso do Task é mais flexível que o "assíncrono aguarda".
Qualquer funcionalidade de sincronização pode ser agrupada por assíncrona.
Não vejo por que devo viver com a duplicação de código para o método de sincronização e assíncrona ou usando hacks.
fonte