Armadilhas: deadlock e async void
ResumoAs três armadilhas mais comuns do assíncrono são: bloquear com
.Result/.Wait()(deadlock), usarasync voide esquecer de aguardar umaTask. Conhecê-las evita bugs difíceis de reproduzir.
1. Objetivos de Aprendizagem#
- Reconhecer e evitar deadlocks por bloqueio síncrono.
- Entender por que
async voidé perigoso. - Evitar "fire-and-forget" acidental.
2. Pré-requisitos#
- Task e CancellationToken (tópicos 11.1 e 11.2).
3. Conceito#
- Bloqueio síncrono (
.Result/.Wait()): misturar chamada síncrona sobre código assíncrono pode travar a aplicação (deadlock), especialmente em contextos com synchronization context. async void: um métodoasync voidnão pode ser aguardado e suas exceções não podem ser capturadas pelo chamador — elas derrubam o processo. Só é aceitável em event handlers.- Task não aguardada: chamar um método assíncrono sem
awaitdispara "fire-and-forget" — exceções são engolidas e a ordem de execução fica imprevisível.
O porquê: esses erros produzem falhas intermitentes e travamentos difíceis de diagnosticar em produção.
4. Mão na Massa#
4.1. Setup#
dotnet new console -n Catalog.AsyncPitfalls
cd Catalog.AsyncPitfalls
4.2. Implementação Passo a Passo#
- Errado — bloqueio síncrono:
// EVITE: pode causar deadlock e desperdiça thread
// var nome = BuscarNomeAsync().Result;
- Certo — async de ponta a ponta:
async Task<string> BuscarNomeAsync()
{
await Task.Delay(100);
return "ok";
}
var nome = await BuscarNomeAsync(); // sem .Result
- Errado vs certo —
async void:
// EVITE (exceções não capturáveis):
// async void ProcessarAsync() { await Task.Delay(10); throw new Exception(); }
// PREFIRA:
async Task ProcessarAsync()
{
await Task.Delay(10);
}
await ProcessarAsync(); // agora dá para aguardar e capturar exceções
4.3. Executando#
dotnet run
5. Exemplo Completo#
Trocar async void por async Task e substituir .Result por await resolve as duas armadilhas mais perigosas de uma vez: exceções passam a ser capturáveis e o risco de deadlock some.
6. Boas Práticas e Armadilhas#
| Faça | Evite |
|---|---|
Async de ponta a ponta (await) | .Result / .Wait() em código de aplicação |
async Task como retorno | async void (exceto event handlers) |
Sempre await (ou guardar/observar) a Task | Fire-and-forget que engole exceções |
Atençãose precisar mesmo executar em segundo plano sem aguardar, use uma abstração apropriada (ex.:
BackgroundService/fila) e trate as exceções explicitamente — nunca deixe uma Task solta.
7. Segurança e Produção#
- Deadlocks e Tasks não observadas causam travamentos e falhas silenciosas sob carga. Habilite análise estática (analisadores do compilador avisam sobre
asyncsemawaite Tasks descartadas).
8. Exercícios#
- Fácil: identifique por que
.Resulté perigoso e reescreva comawait. - Médio: converta um
async voidemasync Taske capture a exceção. - Desafio: demonstre uma exceção "perdida" em fire-and-forget e trate-a corretamente.
9. Resumo#
Evite .Result/.Wait() (deadlock), async void (exceções não capturáveis) e Tasks não aguardadas (exceções engolidas). A regra de ouro é async de ponta a ponta.
10. Próximos Passos#
Encerramos assíncrono. A seguir, o mecanismo que costura toda a aplicação: injeção de dependência.
11. Referências#
- Microsoft Learn — Práticas recomendadas de async
- Microsoft Learn — async void vs async Task
- Microsoft Learn — Tratando exceções em tarefas