Introdução
Em aplicações corporativas com alto volume de processamento, nem tudo pode — ou deve — acontecer dentro de um request HTTP. Importações massivas, consumo de filas de mensageria, sincronizações periódicas, envio de notificações em lote, processamento de eventos de domínio — esses cenários exigem processos em background que executam continuamente, independente de requests, e que sobrevivem a reinícios de forma controlada.
O .NET 8+ oferece uma infraestrutura robusta para isso: os Worker Services e a classe BackgroundService. Baseados no Generic Host (IHost), eles compartilham o mesmo pipeline de injeção de dependências, configuração e logging das Web APIs — mas sem o overhead do Kestrel e do middleware HTTP. Isso significa que tudo o que você já sabe sobre IOptions<T>, ILogger<T>, IServiceProvider e ciclos de vida de DI se aplica diretamente aos Workers.
Neste artigo, vamos explorar os tipos de Workers, como os ciclos de vida Singleton, Scoped e Transient se comportam nesse contexto (com armadilhas que pegam muitos desenvolvedores de surpresa), e como Workers se integram nativamente com paginação de grandes volumes, mensageria para desacoplamento, paralelismo com Parallel e Tasks e programação assíncrona com async/await.
💡 Dica: Se sua aplicação hoje usa um
Task.Run()dentro de um controller para “processar em background”, este artigo vai mostrar a abordagem correta e production-ready para esse cenário.
📦 Código-fonte: A implementação completa deste artigo está no repositório blog-zocateli-sample no GitHub. Clone, explore e adapte ao seu contexto.
O Que É um .NET Worker Service
Um Worker Service é uma aplicação .NET sem interface web, projetada para executar tarefas em background de forma contínua ou periódica. Ele usa o Generic Host — a mesma infraestrutura que uma Web API usa internamente — mas sem o servidor HTTP (Kestrel).
Criando um Worker Service
| |
| |
| |
Generic Host vs WebApplication
| Aspecto | Host.CreateApplicationBuilder | WebApplication.CreateBuilder |
|---|---|---|
| Pipeline de configuração | ✅ Idêntico | ✅ Idêntico |
| Injeção de dependências | ✅ Idêntico | ✅ Idêntico |
| Logging | ✅ Idêntico | ✅ Idêntico |
| IOptions | ✅ Idêntico | ✅ Idêntico |
| Servidor HTTP (Kestrel) | ❌ Não inclui | ✅ Inclui |
| Middleware pipeline | ❌ Não se aplica | ✅ Inclui |
| Endpoints / Controllers | ❌ Não se aplica | ✅ Inclui |
| Uso típico | Background processing, filas | APIs HTTP, web apps |
ℹ️ Informação: Como o pipeline de configuração é idêntico, tudo o que foi explicado em Pipeline de Configuração do .NET 8+ — ordem de providers,
IOptions<T>, Docker secrets,AddKeyPerFile— se aplica diretamente aos Workers.
IHostedService vs BackgroundService
O .NET oferece duas abstrações para serviços em background. Entender a diferença é fundamental:
IHostedService — Controle Total
| |
BackgroundService — Abstração Simplificada
| |
| Aspecto | IHostedService | BackgroundService |
|---|---|---|
| Controle | StartAsync + StopAsync manuais | ExecuteAsync com loop automático |
| Timer/Polling | Implemente manualmente com Timer | Use Task.Delay dentro do loop |
| Cenário ideal | Inicialização/cleanup complexos | Loops de processamento contínuo |
| CancellationToken | Recebido em StartAsync/StopAsync | Recebido em ExecuteAsync |
| Herda de | Interface (IHostedService) | Classe abstrata (BackgroundService) |
⚠️ Atenção: O
ExecuteAsyncdoBackgroundServiceroda em background. Se ele lançar uma exceção não tratada antes do .NET 8, o host continuava rodando silenciosamente com o worker morto. A partir do .NET 8, exceções não tratadas noExecuteAsyncparam o host inteiro por padrão (comportamento configurável viaHostOptions.BackgroundServiceExceptionBehavior).
Tipos de Workers e Suas Aplicabilidades
Na prática corporativa, os Workers se dividem em padrões recorrentes. Escolher o tipo certo para cada cenário evita complexidade desnecessária:
1. Worker de Timer (Polling)
Executa uma tarefa em intervalos regulares. Ideal para sincronizações periódicas, limpeza de dados expirados e health checks:
| |
2. Worker de Fila (Mensageria)
Consome mensagens de uma fila (RabbitMQ, Azure Service Bus, Amazon SQS) em loop contínuo. Este é o padrão mais comum para desacoplamento entre serviços — exatamente o cenário descrito em Gargalo em Banco de Dados: Mensageria e Paginação:
| |
3. Worker de Processamento em Lote (Batch)
Processa grandes volumes de dados em lotes paginados, evitando carregar tudo em memória. Combina naturalmente com a paginação Keyset para processar milhões de registros de forma eficiente:
| |
4. Worker com Paralelismo
Para cenários onde o processamento individual é lento (chamadas HTTP, cálculos pesados), o Worker pode usar paralelismo controlado. Isso se conecta diretamente com os conceitos de Paralelismo em C#: Parallel, PLINQ e Tasks:
| |
💡 Dica: Note o padrão
SemaphoreSlim+Task.WhenAll— ele permite paralelismo controlado sem sobrecarregar recursos. Cada item usa seu próprio escopo de DI (CreateScope), garantindo queDbContexte outros serviços Scoped sejam independentes entre as tasks. Para mais detalhes sobre paralelismo controlado, veja Paralelismo em C#.
Ciclos de Vida no Worker Service
Esta é a seção mais importante do artigo — e a que causa mais bugs em produção. Os ciclos de vida de DI (Singleton, Scoped, Transient) se comportam de forma diferente em Workers comparado a Web APIs, porque não existe o conceito de “request HTTP” para criar escopos automaticamente.
O Problema: Workers São Singleton
O BackgroundService é registrado como Singleton pelo AddHostedService<T>(). Isso significa que todas as suas dependências injetadas via construtor também precisam ser Singleton — ou você terá problemas:
| |
A Solução: IServiceScopeFactory
O padrão correto é injetar IServiceScopeFactory (que é Singleton) e criar escopos manualmente:
| |
Comportamento dos Ciclos de Vida em Workers
| Ciclo de Vida | Injetar no Construtor do Worker? | Via CreateScope? | Comportamento |
|---|---|---|---|
| Singleton | ✅ Sim | ✅ Sim | Mesma instância para toda a vida do host |
| Scoped | ❌ InvalidOperationException | ✅ Sim (obrigatório) | Nova instância por escopo criado manualmente |
| Transient | ⚠️ Funciona, mas perigoso | ✅ Sim (recomendado) | Nova instância a cada resolução — sem escopo, vaza memória |
Por Que Transient no Construtor É Perigoso
| |
⚠️ Atenção: Em Web APIs, o ASP.NET Core cria escopos automaticamente para cada request HTTP. Em Workers, você é responsável por criar escopos manualmente sempre que precisar de serviços Scoped ou Transient. Esquecer de criar o escopo é a causa #1 de memory leaks e connection pool exhaustion em Workers.
Diagrama: Escopo de DI em Web API vs Worker
Em uma Web API, cada request HTTP cria automaticamente um escopo de DI. No Worker, o ExecuteAsync roda em um único escopo Singleton — por isso você precisa criar escopos manualmente com IServiceScopeFactory.CreateScope() para simular o mesmo isolamento.
Workers na Prática Corporativa
Worker como Consumidor de Mensageria
O padrão mais poderoso de Workers em ambiente corporativo é o consumo de filas de mensageria. Em vez de processar tudo sincronicamente dentro da API, a API publica um evento na fila e o Worker consome e processa em background — exatamente o padrão descrito em Gargalo em Banco de Dados com EF Core: Mensageria e Paginação:
| |
Worker para Processamento de Grandes Volumes com Paginação
Quando você precisa processar milhões de registros (migração de dados, recálculo de saldos, reindexação), o Worker se combina com paginação Keyset para processar em blocos sem sobrecarregar a memória:
| |
Worker com Async/Await e CancellationToken
O CancellationToken é o contrato de shutdown graceful do Worker. Quando o host recebe um sinal de parada (SIGTERM no Linux, Ctrl+C, docker stop), ele dispara o token — e o Worker tem um tempo limitado para finalizar o que está fazendo. Esse mecanismo se conecta diretamente com os conceitos de Programação Assíncrona com async/await:
| |
| |
ℹ️ Informação: Em containers Docker/Podman, o
docker stopenviaSIGTERMe aguarda 10 segundos por padrão antes de enviarSIGKILL. Se seu Worker precisa de mais tempo para shutdown graceful, configureShutdownTimeoutno código e--stop-timeoutno container.
Múltiplos Workers no Mesmo Host
Uma aplicação pode registrar vários Workers no mesmo host — cada um roda em sua própria Task:
| |
⚠️ Atenção: Os Workers iniciam na ordem em que foram registrados, mas executam em paralelo. O
StartAsyncde cada um é chamado sequencialmente (na ordem de registro), mas oExecuteAsyncdoBackgroundServiceroda em background imediatamente. Se um Worker depende de outro já estar rodando, useIHostApplicationLifetimepara coordenação.
Quando Separar em Processos Distintos vs Mesmo Host
| Cenário | Mesmo Host | Processos Separados |
|---|---|---|
| Workers leves (timers, health checks) | ✅ Recomendado | Overhead desnecessário |
| Workers com requisitos de escala diferentes | ❌ | ✅ Escalar independentemente |
| Workers com dependências conflitantes | ❌ | ✅ Isolamento de dependências |
| Workers que consomem filas diferentes | Depende do volume | ✅ Escalar por fila |
| Deploy em Kubernetes | ❌ | ✅ Um Deployment por Worker |
| Deploy em VPS/VM | ✅ (systemd services) | Gerenciamento complexo |
Rodando Workers como Serviço do Sistema Operacional
Em produção, Workers rodam como serviços gerenciados pelo sistema operacional:
Linux — systemd
| |
Docker / Podman
| |
| |
Dicas e Boas Práticas
Sempre use
IServiceScopeFactorypara serviços Scoped — nunca injeteDbContext, repositórios ou serviços Scoped diretamente no construtor do Worker. Crie escopos manualmente dentro do loop.Propague o
CancellationTokenpara todas as operações async — deExecuteAsyncaté a chamada ao banco, à fila de mensageria e aoHttpClient. Um Worker que ignora o token demora para parar e pode perder dados no shutdown.Trate exceções dentro do loop — no .NET 8, exceções não tratadas no
ExecuteAsyncparam o host. Use try/catch com retry e logging para evitar que um erro transiente mate o Worker.Configure
HostOptions.ShutdownTimeoutadequadamente — o padrão de 30 segundos pode ser insuficiente para Workers que processam lotes grandes. Alinhe com o--stop-timeoutdo Docker/Podman.Use
IHttpClientFactoryem vez denew HttpClient()— em serviços Singleton como Workers, instanciarHttpClientdiretamente causa vazamento de sockets e DNS stale. OIHttpClientFactorygerencia o pool de handlers.Prefira paginação Keyset para processamento em lote — evite
Skip/Takecom OFFSET para datasets grandes. Keyset (WHERE Id > lastId) mantém performance constante independente do volume.Separe Workers por responsabilidade — um Worker deve fazer uma coisa bem. Workers “canivete suíço” com múltiplas responsabilidades são difíceis de escalar, monitorar e debugar.
Adicione health checks ao Worker — use
IHealthChecke exponha um endpoint HTTP mínimo (comMapHealthChecks) para que orquestradores (Kubernetes, Docker) possam verificar se o Worker está saudável.
Conclusão
Os .NET Workers e Background Services são a solução nativa do .NET para processamento em background — sem gambiarras com Task.Run() dentro de controllers, sem bibliotecas de terceiros para cenários que o framework já resolve. O Generic Host fornece a mesma infraestrutura de configuração, DI e logging das Web APIs, permitindo que equipes compartilhem patterns e conhecimento entre projetos HTTP e Workers.
O ponto mais crítico — e que gera mais bugs em produção — é entender que Workers são Singleton. Isso muda fundamentalmente como você lida com DbContext, repositórios e qualquer serviço Scoped. O padrão IServiceScopeFactory.CreateScope() não é opcional: é a base de todo Worker que acessa banco de dados ou serviços com estado.
Para aplicações corporativas com alto volume, a combinação de Workers com mensageria para desacoplamento, paginação Keyset para processamento em lote, paralelismo controlado com SemaphoreSlim e async/await com CancellationToken forma um arsenal completo para construir sistemas que processam milhões de registros de forma confiável, sem travar a API e sem perder dados no shutdown.
Leia Também
- Gargalo em Banco de Dados com C# e EF Core: Mensageria e Paginação — como Workers consomem filas de mensageria para desafogar o banco de dados em cenários de alto volume.
- Paginação em APIs REST com C# e EF Core 8 — paginação Keyset e Offset para processamento em lote dentro de Workers.
- Paralelismo em C#: Parallel, PLINQ e Tasks — como combinar
Task.WhenAll,SemaphoreSlimeParallel.ForEachAsyncdentro de Workers para paralelismo controlado. - Programação Assíncrona em C#: async/await — fundamentos de async/await e CancellationToken que são a base de todo Worker Service.
- Pipeline de Configuração do .NET 8+: IOptions, Secrets e Docker — o pipeline de configuração compartilhado entre Workers e Web APIs, incluindo Docker secrets com
AddKeyPerFile. - EF Core Migrations: Multi-Projeto, Secrets e Scaffolding — gerenciamento de migrations e secrets em projetos multi-camada com Workers.
Referências
- Worker Services in .NET — Microsoft Learn — documentação oficial de Workers e Background Services no .NET, incluindo exemplos e boas práticas.
- BackgroundService Class — Microsoft Learn — referência da API do
BackgroundService, incluindoExecuteAsynceStopAsync. - IHostedService Interface — Microsoft Learn — referência da interface base para hosted services com
StartAsynceStopAsync. - .NET Generic Host — Microsoft Learn — como o Generic Host configura DI, configuração e logging para Workers e Web APIs.
- Dependency Injection Lifetimes — Microsoft Learn — documentação oficial dos ciclos de vida Singleton, Scoped e Transient no .NET.
- Use scoped services within a BackgroundService — Microsoft Learn — guia oficial para usar
IServiceScopeFactoryem Background Services. - RabbitMQ .NET Client — RabbitMQ Docs — documentação oficial do client RabbitMQ para .NET, incluindo
AsyncEventingBasicConsumer. - Create Windows Services using BackgroundService — Microsoft Learn — como rodar Workers como Windows Services com
UseWindowsService(). - BackgroundServiceExceptionBehavior — Microsoft Learn — configuração do comportamento de exceções não tratadas no
ExecuteAsynca partir do .NET 8. - Repositório blog-zocateli-sample — Workers — Código-fonte completo dos exemplos deste artigo
Ao comentar, você concorda com nossa Política de Privacidade, Termos de Uso e Política de Exclusão de Dados.