Prefácio : estou procurando uma explicação, não apenas uma solução. Eu já conheço a solução.
Apesar de ter passado vários dias estudando artigos do MSDN sobre o padrão assíncrono baseado em tarefas (TAP), assíncrono e aguardando, ainda estou um pouco confuso sobre alguns dos detalhes.
Estou escrevendo um criador de logs para os aplicativos da Windows Store e quero oferecer suporte ao registro assíncrono e síncrono. Os métodos assíncronos seguem a TAP, os síncronos devem esconder tudo isso e parecer e funcionar como métodos comuns.
Este é o método principal de log assíncrono:
private async Task WriteToLogAsync(string text)
{
StorageFolder folder = ApplicationData.Current.LocalFolder;
StorageFile file = await folder.CreateFileAsync("log.log",
CreationCollisionOption.OpenIfExists);
await FileIO.AppendTextAsync(file, text,
Windows.Storage.Streams.UnicodeEncoding.Utf8);
}
Agora, o método síncrono correspondente ...
Versão 1 :
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Wait();
}
Parece correto, mas não funciona. Todo o programa congela para sempre.
Versão 2 :
Hmm .. Talvez a tarefa não tenha sido iniciada?
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.Start();
task.Wait();
}
Isso joga InvalidOperationException: Start may not be called on a promise-style task.
Versão 3:
Hmm ... Task.RunSynchronously
parece promissor.
private void WriteToLog(string text)
{
Task task = WriteToLogAsync(text);
task.RunSynchronously();
}
Isso joga InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.
Versão 4 (a solução):
private void WriteToLog(string text)
{
var task = Task.Run(async () => { await WriteToLogAsync(text); });
task.Wait();
}
Isso funciona. Então, 2 e 3 são as ferramentas erradas. Mas 1? O que há de errado com 1 e qual é a diferença para 4? O que faz 1 causar um congelamento? Há algum problema com o objeto de tarefa? Existe um impasse não óbvio?
fonte
Respostas:
o
await
interior do seu método assíncrono está tentando voltar ao thread da interface do usuário.Como o encadeamento da interface do usuário está ocupado aguardando a conclusão de toda a tarefa, você tem um impasse.
Mover a chamada assíncrona para
Task.Run()
resolver o problema.Como a chamada assíncrona agora está sendo executada em um thread do conjunto de encadeamentos, ela não tenta retornar ao encadeamento da interface do usuário e, portanto, tudo funciona.
Como alternativa, você pode ligar
StartAsTask().ConfigureAwait(false)
antes de aguardar a operação interna para fazer com que ela volte ao pool de threads em vez do thread da interface do usuário, evitando completamente o conflito.fonte
ConfigureAwait(false)
é a solução apropriada neste caso. Como não é necessário chamar os retornos de chamada no contexto capturado, não deveria. Sendo um método de API, ele deve tratá-lo internamente, em vez de forçar todos os chamadores a sair do contexto da interface do usuário.Microsoft.Bcl.Async
.Chamar
async
código a partir de código síncrono pode ser bastante complicado.Explico todos os motivos desse impasse no meu blog . Em resumo, há um "contexto" que é salvo por padrão no início de cada um
await
e usado para retomar o método.Portanto, se isso for chamado em um contexto de interface do usuário, quando a
await
conclusão for concluída, oasync
método tentará entrar novamente nesse contexto para continuar executando. Infelizmente, o código que usaWait
(ouResult
) bloqueia um segmento nesse contexto, portanto, oasync
método não pode ser concluído.As diretrizes para evitar isso são:
ConfigureAwait(continueOnCapturedContext: false)
o máximo possível. Isso permite que o seuasync
métodos continuem executando sem precisar entrar novamente no contexto.async
todo o caminho. Use emawait
vez deResult
ouWait
.Se seu método é naturalmente assíncrono, você (provavelmente) não deve expor um invólucro síncrono .
fonte
async
como eu faria isso e evito uma situação de incêndio e esquecimento.await
é suportado emcatch
blocos a partir do VS2015. Se você estiver em uma versão mais antiga, poderá atribuir a exceção a uma variável local e fazer oawait
depois do bloco catch .Aqui está o que eu fiz
funcionando muito bem e sem bloquear o thread da interface do usuário
fonte
Com um pequeno contexto de sincronização personalizado, a função de sincronização pode aguardar a conclusão da função assíncrona, sem criar conflito. Aqui está um pequeno exemplo para o aplicativo WinForms.
fonte