Eu tenho um aplicativo .Net 4.5 multicamadas chamando um método usando C #'s new async
e await
palavras - chave que simplesmente trava e não consigo ver o porquê.
Na parte inferior, tenho um método assíncrono que estende nosso utilitário de banco de dados OurDBConn
(basicamente um wrapper para os objetos DBConnection
e subjacentes DBCommand
):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Então eu tenho um método assíncrono de nível médio que chama isso para obter alguns totais de execução lenta:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Finalmente, tenho um método de IU (uma ação MVC) que é executado de forma síncrona:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
O problema é que essa última linha fica para sempre. Ele faz a mesma coisa se eu ligar asyncTask.Wait()
. Se eu executar o método SQL lento diretamente, leva cerca de 4 segundos.
O comportamento que estou esperando é que, quando chegar ao fim asyncTask.Result
, se não estiver concluído, espere até que termine e, assim que terminar, retorne o resultado.
Se eu avançar com um depurador, a instrução SQL será concluída e a função lambda será concluída, mas a return result;
linha de GetTotalAsync
nunca será alcançada.
Alguma ideia do que estou fazendo de errado?
Alguma sugestão de onde eu preciso investigar para corrigir isso?
Isso poderia ser um impasse em algum lugar e, se for o caso, existe alguma maneira direta de encontrá-lo?
SynchronizationContext
.async
/await
.Este é o
async
cenário clássico de deadlock misto , conforme descrevo em meu blog . Jason descreveu bem: por padrão, um "contexto" é salvo a cadaawait
e usado para continuar oasync
método. Este "contexto" é o atual aSynchronizationContext
menos que sejanull
, neste caso é o atualTaskScheduler
. Quando oasync
método tenta continuar, ele primeiro entra novamente no "contexto" capturado (neste caso, um ASP.NETSynchronizationContext
). O ASP.NETSynchronizationContext
permite apenas um thread no contexto por vez, e já existe um thread no contexto - o thread está bloqueadoTask.Result
.Existem duas diretrizes que evitarão esse impasse:
async
todo o caminho para baixo. Você mencionou que "não pode" fazer isso, mas não tenho certeza do motivo. A ASP.NET MVC no .NET 4.5 certamente pode oferecer suporte aasync
ações, e não é uma mudança difícil de fazer.ConfigureAwait(continueOnCapturedContext: false)
o máximo possível. Isso substitui o comportamento padrão de retomar no contexto capturado.fonte
ConfigureAwait(false)
garantia de que a função atual recomeça em um contexto diferente?async
ação sem interromper a maneira como isso funciona do lado do cliente. Eu certamente planejo investigar essa opção a longo prazo.ConfigureAwait(false)
da árvore de chamadas teria resolvido o problema do OP.async
não afeta o lado do cliente de forma alguma. Explico isso em outra postagem do blog,async
Não altera o protocolo HTTP .async
"crescer" por meio da base de código. Se o seu método de controlador pode depender de operações assíncronas, o método da classe base deve retornarTask<ActionResult>
. Fazer a transição de um grande projeto para o qualasync
é sempre complicado porque misturarasync
e sincronizar o código é difícil e complicado. Oasync
código puro é muito mais simples.Eu estava na mesma situação de impasse, mas, no meu caso, chamando um método assíncrono de um método de sincronização, o que funciona para mim foi:
essa é uma boa abordagem, alguma ideia?
fonte
Apenas para adicionar à resposta aceita (não há representante suficiente para comentar), tive esse problema ao bloquear o uso de
task.Result
, embora todos os eventosawait
abaixo tivessemConfigureAwait(false)
, como neste exemplo:O problema realmente estava no código da biblioteca externa. O método de biblioteca assíncrona tentou continuar no contexto de sincronização de chamada, independentemente de como eu configurei o await, levando ao deadlock.
Portanto, a resposta foi lançar minha própria versão do código da biblioteca externa
ExternalLibraryStringAsync
, para que ela tivesse as propriedades de continuação desejadas.resposta errada para fins históricos
Depois de muita dor e angústia, encontrei a solução enterrada nesta postagem do blog (Ctrl-f para 'impasse'). Ele gira em torno do uso
task.ContinueWith
, em vez do básicotask.Result
.Exemplo de deadlock anterior:
Evite o impasse como este:
fonte
Task
ser concluído e não fornecendo ao chamador nenhum meio de determinar quando a mutação do objeto retornado realmente ocorre.GetFooSynchronous
método?Task
vez de bloqueio.resposta rápida: mude esta linha
para
porque? você não deve usar .result para obter o resultado das tarefas dentro da maioria dos aplicativos, exceto aplicativos de console, se você fizer isso o seu programa irá travar quando chegar lá
você também pode tentar o código abaixo se quiser usar .Result
fonte