Pular para o conteúdo
.NETArmadilhas: deadlock e async void
Módulo 02Fundamentos de Aplicação

Armadilhas: deadlock e async void

intermediario 24 min de leitura·Atualizado · .NET 9
Resumo

As três armadilhas mais comuns do assíncrono são: bloquear com .Result/.Wait() (deadlock), usar async void e esquecer de aguardar uma Task. 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étodo async void nã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 await dispara "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#

Shell
dotnet new console -n Catalog.AsyncPitfalls
cd Catalog.AsyncPitfalls

4.2. Implementação Passo a Passo#

  1. Errado — bloqueio síncrono:
C#
// EVITE: pode causar deadlock e desperdiça thread
// var nome = BuscarNomeAsync().Result;
  1. Certo — async de ponta a ponta:
C#
async Task<string> BuscarNomeAsync()
{
    await Task.Delay(100);
    return "ok";
}

var nome = await BuscarNomeAsync(); // sem .Result
  1. Errado vs certo — async void:
C#
// 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#

Shell
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çaEvite
Async de ponta a ponta (await).Result / .Wait() em código de aplicação
async Task como retornoasync void (exceto event handlers)
Sempre await (ou guardar/observar) a TaskFire-and-forget que engole exceções
Atenção

se 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 async sem await e Tasks descartadas).

8. Exercícios#

  • Fácil: identifique por que .Result é perigoso e reescreva com await.
  • Médio: converta um async void em async Task e 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#

11-03 — Armadilhas: deadlock e async void | Curso ASP.NET Core