Como Executar Tarefas em Segundo Plano no ASP.NET Core
Introdução
O processamento de arquivos (por exemplo, exportar uma apresentação para PDF) é uma tarefa típica do lado do servidor. Executá‑lo dentro do manipulador de requisição (enquanto o cliente aguarda) tem as seguintes desvantagens:
- Interface pobre. A página congela e o usuário precisa aguardar o resultado. Recarregar a página cancela a tarefa.
- Tempo limite de operação. Não podemos garantir que o processamento será concluído dentro de um período fixo, portanto o usuário provavelmente verá um “tempo limite de operação”.
- Baixa taxa de transferência e escalabilidade. O ASP.NET Core foi projetado para processar muitas requisições de forma assíncrona. Tarefas intensivas em CPU e de longa duração bloqueiam threads e reduzem a taxa de transferência do servidor.
- Baixa tolerância a falhas. Se algo der errado durante uma tarefa de longa duração (por exemplo, um problema de conectividade), o processamento falha e deve ser reiniciado do início.
Uma abordagem melhor é agendar o trabalho assíncronamente, processá‑lo em segundo plano e retornar o resultado quando estiver pronto.
Neste modelo, o usuário pode ver o status atual (e pode sair ou recarregar a página), os recursos do servidor podem ser dimensionados eficientemente e ajustados de forma flexível, e uma política de repetição pode ser aplicada.
Uma solução típica de processamento em segundo plano inclui:
- Uma API para agendar o trabalho.
- Uma API para rastrear o status do trabalho.
- Um worker em segundo plano para processar trabalhos agendados.
- Uma API para armazenar e recuperar o resultado.
Exemplo de Tarefa em Segundo Plano
Para demonstrar essa abordagem, considere o exemplo de aplicativo web ASP.NET Core 3.1. O app inclui uma página onde o usuário pode enviar uma apresentação e clicar em Exportar para PDF; a apresentação é então enviada e convertida para PDF por um worker em segundo plano.
Aplicativo Web
O aplicativo web de exemplo (projeto BackgroundJobDemo) inclui:
- Página de upload de arquivos (Razor page “Upload”).
- Página de progresso (Razor page “Progress” com algumas funções JavaScript que verificam e exibem o status).
- Controlador (
JobStatusController) que fornece o status do processamento (api/status/{jobId}). - Controlador (
JobResultController) que retorna o arquivo PDF exportado (api/result/{id}). - Worker em segundo plano baseado no serviço de hospedagem ASP.NET Core (veja a classe
WorkerService).
Páginas Razor, controladores e o worker em segundo plano delegam o trabalho real por meio de interfaces definidas no projeto BackgroundJobDemo.Common. Implementações concretas de gerenciamento e processamento de trabalhos são fornecidas em projetos separados (BackgroundJobDemo.Local, BackgroundJobDemo.Aws, etc.) e podem ser trocadas no método Startup.ConfigureServices.
Para fins de demonstração, a página “Upload” utiliza binding de modelo em buffer, mas para uploads de arquivos grandes, streaming sem buffer é recomendado. Para produção, considere os aspectos de segurança relevantes. A página “Progress” consulta o status do trabalho agendado via JavaScript a cada dois segundos (esse intervalo é configurável). A consulta periódica é típica, mas para cenários mais avançados pode ser necessário notificações em tempo real via WebSockets (comunicações em tempo real estão fora do escopo deste artigo). SignalR é uma ferramenta simples mas poderosa para comunicações em tempo real.
Hospedar o worker em segundo plano no processo do servidor é conveniente para aplicações simples, mas tem desvantagens. Uma abordagem mais robusta e escalável é implantar o worker em um processo separado (veja, por exemplo, a aplicação console BackgroundJobDemo.Worker).
Implementação Básica
O projeto BackgroundJobDemo.Local fornece uma implementação simples de gerenciamento de trabalhos usando um banco de dados SQLite (o caminho do banco de dados é configurado via LocalConfig.DbFilePath; veja Startup.ConfigureServices). Arquivos enviados e processados são armazenados no sistema de arquivos (o caminho da pasta de armazenamento é configurado via LocalConfig.FileStorageFolderPath; veja Startup.ConfigureServices). Para melhor tolerância a falhas e desempenho em aplicações reais, o agendamento de trabalhos deve ser implementado por meio de filas de mensagens (por exemplo, RabbitMQ, AWS SQS, Azure Storage Queue).
Implementação Distribuída Baseada na Amazon Web Services
O projeto BackgroundJobDemo.Aws implementa o processamento de trabalhos na Amazon Web Services e demonstra uma arquitetura distribuída horizontalmente escalável. Ele inclui os seguintes componentes:
- Aplicativo web — interage com o usuário e agenda tarefas de exportação de PPTX para PDF, etc.
- Worker — processa exportações (in-process, out-of-process ou AWS Lambda).
- Fila de mensagens — armazena tarefas a serem processadas (Amazon SQS).
- Armazenamento de arquivos — armazena arquivos enviados e processados (Amazon S3).
- Armazenamento chave‑valor — rastreia o status de processamento das tarefas (Amazon DynamoDB).
Uma arquitetura distribuída típica depende de filas de mensagens: o aplicativo web coloca tarefas em segundo plano em uma fila; um worker em segundo plano recupera tarefas da fila e executa o trabalho necessário. Isso desacopla os componentes e torna o processamento assíncrono e confiável. A fila garante entrega e usa um visibility timeout: quando um worker pega uma mensagem, ela se torna invisível para outros workers; apenas o worker que está processando a remove ao concluir. Se o processamento não terminar dentro do visibility timeout (por exemplo, devido a uma falha ou problema de rede), a mensagem não processada torna‑se visível novamente.
Nossa implementação usa o Amazon Simple Queue Service (SQS), uma fila de mensagens totalmente gerenciada para microsserviços, sistemas distribuídos e aplicações serverless.
Filas de mensagens destinam‑se a mensagens leves (por exemplo, o limite de tamanho de mensagem do SQS é 256 KB), portanto uma mensagem deve conter apenas a descrição da tarefa. Dados pesados (como arquivos a serem processados) devem ser armazenados separadamente e referenciados na mensagem. O Amazon S3 é usado para armazenar arquivos enviados e processados.
Um armazenamento chave‑valor é necessário para persistir e recuperar resultados de trabalhos por ID. O exemplo usa o Amazon DynamoDB, um serviço de banco de dados NoSQL rápido e flexível.
Para executar o aplicativo de demonstração com a Amazon Web Services:
- Na mesma região da AWS, crie e configure:
- uma fila SQS,
- um bucket S3,
- uma tabela DynamoDB.
- Conecte o aplicativo web a esses serviços chamando AddAws em
Startup.ConfigureServices, fornecendo a URL da fila SQS, o nome do bucket S3, o nome da tabela DynamoDB e a região da AWS.