DEV Community

Cover image for Graceful shutdown no .NET: o CancellationToken que você está ignorando vai corromper seus dados
Paulo Walraven
Paulo Walraven

Posted on

Graceful shutdown no .NET: o CancellationToken que você está ignorando vai corromper seus dados

Seu serviço está no meio de processar um pagamento quando o container é reiniciado. Se você não tratou o cancelamento, parabéns: você acabou de deixar dados em memória que nunca chegaram ao banco — e um registro que ninguém mais consegue confiar.

Ao final deste post você vai entender por que o CancellationToken no seu loop de background não é detalhe de estilo, mas uma questão de integridade de dados — e qual é o único padrão correto para respeitá-lo.

Em produção, sua aplicação reinicia o tempo todo

Talvez no seu ambiente local a aplicação suba uma vez e fique de pé por horas. Em produção é o oposto. Aplicações são reiniciadas constantemente:

  • Serviços são atualizados em novos deploys.
  • Containers são reimplementados (redeploy).
  • Serviços escalam para cima e para baixo conforme a carga.

Toda vez que isso acontece, o trabalho em background precisa parar de um jeito específico: graciosamente, não abruptamente. A diferença entre os dois é onde mora o bug.

O CancellationToken é o sinal de "hora de parar"

Quando a aplicação começa a desligar, o host dispara um sinal. Esse sinal chega ao seu hosted service através do CancellationToken recebido no ExecuteAsync:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    // stoppingToken é acionado quando a aplicação começa o shutdown
}
Enter fullscreen mode Exit fullscreen mode

O token é acionado quando a aplicação começa a desligar. Ele é o seu aviso prévio. O problema é que respeitá-lo é opcional do ponto de vista do compilador — e é muito fácil simplesmente não usá-lo.

O padrão correto (e o erro silencioso)

O ExecuteAsync te entrega o token, mas nada te obriga a verificá-lo dentro do loop. Esse é o erro silencioso: escrever um loop que ignora o sinal de shutdown.

O padrão absolutamente correto é verificar o token na condição do loop:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        // faz o trabalho em background

        // repassa o token para as chamadas async também
        await Task.Delay(1000, stoppingToken);
    }
}
Enter fullscreen mode Exit fullscreen mode

Dois pontos que fazem esse padrão funcionar:

  • while (!stoppingToken.IsCancellationRequested) garante que, no início de cada iteração, o loop verifica se já é hora de parar.
  • Repassar o token para chamadas assíncronas (como Task.Delay) faz a espera ser interrompida imediatamente quando o shutdown chega — em vez de segurar a aplicação até o fim do delay.

O resultado: a tarefa para rapidamente quando o shutdown começa, em vez de ser morta no meio do caminho.

Comparação entre um loop interrompido abruptamente e um loop que respeita o cancelamento

Por que isso importa de verdade

Vale insistir nesse ponto, porque ele é fácil de subestimar. Imagine que seu serviço está processando pagamentos ou processando documentos.

Se a aplicação desliga, você está no meio de uma operação e não tratou o cancelamento, o estrago pode ser:

  • Alguns dados ficaram em memória e nunca foram consolidados no banco.
  • Ou os dados no banco mudaram, mas a operação não terminou.
  • Resultado: dados incompletos e não confiáveis — você não pode confiar na precisão deles.

É exatamente esse tipo de inconsistência entre memória e banco que respeitar o CancellationToken evita. Não é sobre código bonito; é sobre não corromper o estado do seu sistema toda vez que um deploy acontece.

Conclusão

Em produção o shutdown não é exceção — é rotina. O CancellationToken que o ExecuteAsync te entrega é o que separa um serviço que para de forma limpa de um que deixa rastros de dados corrompidos a cada reinício. Use while (!stoppingToken.IsCancellationRequested) e repasse o token para suas chamadas async, sempre.

Vá ao seu hosted service e verifique agora: seu loop realmente checa o token? E suas operações longas conseguem ser interrompidas no meio sem deixar lixo? Se a resposta for não, você tem um candidato a bug de produção esperando o próximo deploy. No próximo post, fechamos o módulo decidindo quando manter o hosted service na API e quando movê-lo para um worker service dedicado.

Top comments (0)