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
}
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);
}
}
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.
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)