Estou trabalhando com uma nova base de código que faz uso pesado de async / waitit. A maioria das pessoas da minha equipe também é relativamente nova em assíncrona / aguardar. Geralmente, tendemos a manter as Práticas recomendadas conforme especificado pela Microsoft , mas geralmente precisamos que nosso contexto flua através da chamada assíncrona e esteja trabalhando com bibliotecas que não o fazem ConfigureAwait(false)
.
Combine todas essas coisas e encontraremos impasses assíncronos descritos no artigo ... semanalmente. Eles não aparecem durante o teste de unidade, porque nossas fontes de dados simuladas (geralmente via Task.FromResult
) não são suficientes para acionar o impasse. Portanto, durante os testes de tempo de execução ou integração, algumas chamadas de serviço saem para almoçar e nunca mais retornam. Isso mata os servidores e geralmente atrapalha as coisas.
O problema é que rastrear onde o erro foi cometido (geralmente apenas não sendo assíncrono até o fim) geralmente envolve inspeção manual de código, que consome tempo e não pode ser automatizada.
Qual é a melhor maneira de diagnosticar o que causou o impasse?
async
artigos desse cara ?Respostas:
Ok - não tenho certeza se o seguinte será de alguma ajuda para você, porque fiz algumas suposições no desenvolvimento de uma solução que pode ou não ser verdadeira no seu caso. Talvez minha "solução" seja muito teórica e funcione apenas para exemplos artificiais - eu não realizei nenhum teste além dos itens abaixo.
Além disso, eu consideraria o seguinte mais uma solução alternativa do que uma solução real, mas, considerando a falta de respostas, acho que ainda pode ser melhor do que nada (continuei observando sua pergunta aguardando uma solução, mas não vendo uma sendo postada, comecei a jogar por aí com o problema).
Mas basta dizer: digamos que temos um serviço de dados simples que pode ser usado para recuperar um número inteiro:
Uma implementação simples usa código assíncrono:
Agora, surge um problema, se estivermos usando o código "incorretamente", conforme ilustrado por esta classe.
Foo
acessa incorretamente, emTask.Result
vez deawait
ing o resultado, comoBar
:O que precisamos agora é de uma maneira de escrever um teste que seja bem-sucedido ao ligar,
Bar
mas falhe ao ligarFoo
(pelo menos se eu entendi a pergunta corretamente ;-)).Vou deixar o código falar; aqui está o que eu criei (usando testes do Visual Studio, mas deve funcionar usando o NUnit também):
DataServiceMock
utilizaTaskCompletionSource<T>
. Isso nos permite definir o resultado em um ponto definido na execução do teste que leva ao teste a seguir. Observe que estamos usando um representante para devolver o TaskCompletionSource de volta ao teste. Você também pode colocar isso no método Initialize do teste e usar propriedades.O que está acontecendo aqui é que primeiro verificamos que podemos deixar o método sem bloquear (isso não funcionaria se alguém fosse acessado
Task.Result
- nesse caso, teríamos um tempo limite porque o resultado da tarefa não seria disponibilizado até que o método retornasse )Em seguida, definimos o resultado (agora o método pode ser executado) e verificamos o resultado (em um teste de unidade, podemos acessar o Task.Result, pois na verdade queremos que o bloqueio ocorra).
Classe de teste completa -
BarTest
obtém êxito eFooTest
falha conforme desejado.E uma pequena turma auxiliar para testar deadlocks / timeouts:
fonte
Aqui está uma estratégia que eu usei em um aplicativo enorme e muito, muito multithread:
Primeiro, você precisa de alguma estrutura de dados em torno de um mutex (infelizmente) e não cria um diretório de chamadas de sincronização. Nessa estrutura de dados, há um link para qualquer mutex bloqueado anteriormente. Todo mutex tem um "nível" começando em 0, que você atribui quando o mutex é criado e nunca pode mudar.
E a regra é: se um mutex estiver bloqueado, você só deve bloquear outros mutexes em um nível inferior. Se você seguir essa regra, não poderá ter conflitos. Quando você encontra uma violação, seu aplicativo ainda está funcionando perfeitamente.
Quando você encontra uma violação, há duas possibilidades: Você pode ter atribuído os níveis incorretamente. Você bloqueou A seguido pelo bloqueio B, então B deveria ter um nível mais baixo. Então você fixa o nível e tenta novamente.
A outra possibilidade: você não pode consertar. Algum código seu bloqueia A seguido pelo bloqueio B, enquanto outro código bloqueia B seguido pelo bloqueio A. Não há como atribuir os níveis para permitir isso. E é claro que esse é um impasse em potencial: se os dois códigos forem executados simultaneamente em threads diferentes, há uma chance de impasse.
Após a introdução, houve uma fase bastante curta em que os níveis tiveram que ser ajustados, seguida por uma fase mais longa em que foram encontrados possíveis impasses.
fonte
Você está usando o Async / Await para poder paralelizar chamadas caras como em um banco de dados? Dependendo do caminho de execução no banco de dados, isso pode não ser possível.
A cobertura de teste com async / waitit pode ser desafiadora e não há nada como o uso real da produção para encontrar bugs. Um padrão que você pode considerar é passar um ID de correlação e registrá-lo na pilha e, em seguida, ter um tempo limite em cascata que registra o erro. Esse é mais um padrão SOA, mas pelo menos daria uma noção de onde ele vem. Usamos isso com o Splunk para encontrar impasses.
fonte