Por que sua aplicação C# precisa de filas e tarefas em segundo plano?
\n\nImagine: você inicia uma aplicação web em C#, o usuário clica no botão "Enviar relatório", e… o servidor trava por 30 segundos enquanto gera o PDF. Familiar? Em 2024, os usuários não toleram lentidão. A solução — filas e tarefas em segundo plano. Elas transformam o inferno síncrono em um paraíso assíncrono.
\n\nNeste artigo, vamos analisar como organizar corretamente o processamento em segundo plano em C#: desde o nativo Channel<T> até o industrial RabbitMQ. Você aprenderá como tirar o trabalho pesado do fluxo principal sem quebrar a arquitetura. Vamos lá!
O que é uma fila de tarefas e por que usá-la em C#?
\n\nUma fila (queue) é uma estrutura de dados FIFO (First In, First Out). No contexto do C#, é um mecanismo que permite adiar a execução de uma tarefa para não bloquear o fluxo principal. Uma tarefa em segundo plano é um código executado "nos bastidores" enquanto o usuário continua navegando no site.
\n\nCenários típicos:
\n- \n
- Envio de notificações por e-mail (100 e-mails = 100 requisições ao SMTP) \n
- Geração de relatórios e arquivos Excel \n
- Processamento de imagens carregadas (redimensionamento, marca d'água) \n
- Integração com APIs externas (respostas lentas) \n
- Registro em banco de dados (para não travar a interface do usuário) \n
Sem filas, a aplicação C# vai "travar" em cada requisição pesada. Com filas, você simplesmente coloca a tarefa em um buffer e responde ao usuário: "Pronto, estamos processando".
\n\nOpção 1: Tarefas em segundo plano com BackgroundService (nativo do .NET)
\n\nA maneira mais simples é usar BackgroundService do Microsoft.Extensions.Hosting. Esta é a classe base para criar serviços em segundo plano no ASP.NET Core. É ideal para cenários simples onde não é necessário roteamento complexo de mensagens.
Exemplo: processador em segundo plano com Channel<T>
\n\nVamos escrever um Producer-Consumer simples. O Producer (controller) coloca a tarefa no canal, o Consumer (BackgroundService) pega e executa.
\n\n// Passo 1. Criar um canal (fila thread-safe)\npublic class TaskChannel\n{\n private readonly Channel<string> _channel = \n Channel.CreateBounded<string>(new BoundedChannelOptions(100)\n {\n FullMode = BoundedChannelFullMode.Wait\n });\n\n public ChannelWriter<string> Writer => _channel.Writer;\n public ChannelReader<string> Reader => _channel.Reader;\n}\n\n// Passo 2. BackgroundService que lê do canal\npublic class EmailBackgroundService : BackgroundService\n{\n private readonly TaskChannel _taskChannel;\n\n public EmailBackgroundService(TaskChannel taskChannel)\n {\n _taskChannel = taskChannel;\n }\n\n protected override async Task ExecuteAsync(CancellationToken stoppingToken)\n {\n await foreach (var emailData in _taskChannel.Reader.ReadAllAsync(stoppingToken))\n {\n // Simulação de envio de e-mail (2 segundos)\n await Task.Delay(2000, stoppingToken);\n Console.WriteLine($"E-mail enviado: {emailData}");\n }\n }\n}\n\n// Passo 3. Controller que coloca as tarefas\n[ApiController]\n[Route("api/email")]\npublic class EmailController : ControllerBase\n{\n private readonly TaskChannel _taskChannel;\n\n public EmailController(TaskChannel taskChannel)\n {\n _taskChannel = taskChannel;\n }\n\n [HttpPost("send")]\n public IActionResult SendEmail([FromBody] string emailData)\n {\n _taskChannel.Writer.TryWrite(emailData);\n return Accepted(new { message = "E-mail colocado na fila" });\n }\n}\n\n// Passo 4. Registro no Program.cs\nbuilder.Services.AddSingleton<TaskChannel>();\nbuilder.Services.AddHostedService<EmailBackgroundService>();\n\nO que está acontecendo aqui? O usuário chama POST /api/email/send — os dados vão para o ca