Me deparei com algumas práticas recomendadas para programação assíncrona usando c # 's async
/ await
keywords (eu sou novo no c # 5.0).
Um dos conselhos dados foi o seguinte:
Estabilidade: Conheça seus contextos de sincronização
... Alguns contextos de sincronização são não reentrantes e de thread único. Isso significa que apenas uma unidade de trabalho pode ser executada no contexto em um determinado momento. Um exemplo disso é o thread da IU do Windows ou o contexto de solicitação ASP.NET. Nesses contextos de sincronização de thread único, é fácil travar você mesmo. Se você gerar uma tarefa de um contexto de thread único e esperar por essa tarefa no contexto, seu código de espera pode estar bloqueando a tarefa em segundo plano.
public ActionResult ActionAsync()
{
// DEADLOCK: this blocks on the async task
var data = GetDataAsync().Result;
return View(data);
}
private async Task<string> GetDataAsync()
{
// a very simple async method
var result = await MyWebService.GetDataAsync();
return result.ToString();
}
Se eu tentar dissecá-lo sozinho, o thread principal gerará um novo em MyWebService.GetDataAsync();
, mas, como o thread principal aguarda lá, ele espera o resultado em GetDataAsync().Result
. Enquanto isso, digamos que os dados estejam prontos. Por que o thread principal não continua sua lógica de continuação e retorna um resultado de string GetDataAsync()
?
Alguém pode me explicar por que há um impasse no exemplo acima? Estou completamente sem noção sobre qual é o problema ...
fonte
var data = GetDataAsync().Result;
é uma linha de código que nunca deve ser feita em um contexto que você não deve bloquear (solicitação de UI ou ASP.NET). Mesmo que isso não ocorra, ele está bloqueando o thread por um período de tempo indeterminado. Então, basicamente, é um exemplo terrível. [Você precisa sair do thread da IU antes de executar um código como esse, ou usarawait
lá também, como Toni sugere.]Respostas:
Dê uma olhada neste exemplo , Stephen tem uma resposta clara para você:
Outro link que você deve ler: Await, e UI e deadlocks! Oh meu!
fonte
GetDataAsync().Result;
será executado quando a tarefa retornada por forGetDataAsync()
concluída, enquanto isso bloqueia o thread de interface do usuárioreturn result.ToString()
) é enfileirada no thread de IU para execuçãoGetDataAsync()
será concluída quando sua continuação na fila for executadaImpasse!
O impasse pode ser resolvido por alternativas fornecidas para evitar o Fato 1 ou o Fato 2.
var data = await GetDataAsync()
, que permite que o UI thread continue em execuçãovar data = Task.Run(GetDataAsync).Result
, use , que postará a continuação para o contexto de sincronização de um thread de pool de threads. Isso permite que a tarefa retornada porGetDataAsync()
seja concluída.Isso é muito bem explicado em um artigo de Stephen Toub , no meio do caminho, onde ele usa o exemplo de
DelayAsync()
.fonte
var data = Task.Run(GetDataAsync).Result
isso é novo para mim. Sempre pensei que o exterior.Result
estaria disponível assim que a primeira esperaGetDataAsync
fosse atingida, edata
sempre estarádefault
. Interessante.Eu estava apenas brincando com esse problema novamente em um projeto ASP.NET MVC. Quando você deseja chamar
async
métodos de aPartialView
, não tem permissão para fazer oPartialView
async
. Você receberá uma exceção se o fizer.Você pode usar a seguinte solução alternativa simples no cenário em que deseja chamar um
async
método de um método de sincronização:SynchronizationContext
SynchronizationContext
Exemplo:
fonte
Outro ponto principal é que você não deve bloquear no Tasks e usar async até o fim para evitar deadlocks. Então, tudo será bloqueio assíncrono e não síncrono.
fonte
Uma solução alternativa é usar um
Join
método de extensão na tarefa antes de solicitar o resultado.O código é parecido com este:
Onde o método de junção é:
Não sou o suficiente no domínio para ver as desvantagens desta solução (se houver)
fonte