Serviços de trabalho no .NET

Há inúmeros motivos para criar serviços de execução longa, como:

  • Processamento de dados com uso intensivo de CPU.
  • Enfileiramento de itens de trabalho em segundo plano.
  • Execução de operação cronometrada segundo agendamento.

O processamento de serviço em segundo plano geralmente não envolve uma interface do usuário, mas ela pode ser criada em torno deles. Nos primórdios do .NET Framework, os desenvolvedores do Windows podiam criar serviços Windows por esses motivos. Agora, com o .NET, você pode usar o BackgroundService, que é uma implementação de IHostedService, ou implementar o seu.

Com o .NET, você não está mais restrito ao Windows. Você pode desenvolver serviços multiplataforma em segundo plano. Os serviços hospedados estão prontos para registro em log, configuração e DI (injeção de dependência). Eles fazem parte do conjunto de extensões de bibliotecas, o que significa que são fundamentais para todas as cargas de trabalho do .NET que funcionam com o host genérico.

Importante

Instalar o SDK do .NET também instala o Microsoft.NET.Sdk.Worker e o modelo de trabalho. Em outras palavras, depois de instalar o SDK do .NET, você pode criar um trabalho usando o novo comando de trabalho do dotnet. Se você estiver usando o Visual Studio, o modelo ficará oculto até que a carga de trabalho opcional de ASP.NET e desenvolvimento da Web seja instalada.

Terminologia

Muitos termos são usados erroneamente sinônimos. Esta seção define alguns desses termos para tornar a intenção deles neste artigo mais evidente.

  • Serviço em segundo plano: o tipo BackgroundService.
  • Serviço hospedado: implementações de IHostedService ou o próprio IHostedService.
  • Serviço de execução: qualquer serviço que seja executado continuamente.
  • Serviço Windows: a infraestrutura do Serviço Windows, originalmente centrada no .NET Framework, mas agora acessível por meio do .NET.
  • Serviço de trabalho: o modelo de Serviço de Trabalho.

Modelo de serviço de trabalho

O modelo de Serviço de Trabalho está disponível na CLI do .NET e no Visual Studio. Para obter mais informações, confira CLI do .NET – dotnet new workerModelo. O modelo consiste em uma classe Program e Worker.

using App.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

IHost host = builder.Build();
host.Run();

A classe Program anterior:

Padrões de modelo

O modelo de trabalho não habilita a coleta de lixo (GC) do servidor por padrão, pois há inúmeros fatores que desempenham um papel na determinação de sua necessidade. Todos os cenários que exigem serviços de execução longa devem considerar as implicações de desempenho desse padrão. Para habilitar o GC do servidor, adicione o nó ServerGarbageCollection ao arquivo de projeto:

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Compensações e considerações

Enabled Desabilitado
Gerenciamento eficiente de memória: recupera automaticamente a memória não utilizada para evitar vazamentos de memória e otimizar o uso de recursos. Melhor desempenho em tempo real: evita possíveis pausas ou interrupções causadas pela coleta de lixo em aplicativos sensíveis à latência.
Estabilidade a longo prazo: ajuda a manter o desempenho estável em serviços de longa execução gerenciando a memória por longos períodos. Eficiência de recursos: pode conservar recursos de CPU e memória em ambientes restritos a recursos.
Manutenção reduzida: minimiza a necessidade de gerenciamento manual de memória, simplificando a manutenção. Controle de memória manual: fornece controle refinado sobre a memória para aplicativos especializados.
Comportamento previsível: contribui para um comportamento de aplicativo consistente e previsível. Adequado para processos de curta duração: minimiza a sobrecarga da coleta de lixo para processos de curta duração ou efêmeros.

Para obter mais informações sobre considerações de desempenho, confira a GC do Servidor. Para obter mais informações sobre como configurar o GC do servidor, confira exemplos de configuração do GC do Servidor.

Classe de trabalho

Quanto ao Worker, o modelo fornece uma implementação simples.

namespace App.WorkerService;

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

A classe anterior Worker é uma subclasse de BackgroundService, que implementa IHostedService. O BackgroundService é um abstract class e requer que a subclasse implemente BackgroundService.ExecuteAsync(CancellationToken). Na implementação do modelo, ExecuteAsync completa um ciclo por segundo, registrando a data e a hora atuais até que o processo seja sinalizado para cancelar.

O arquivo de projeto

O modelo de trabalho depende do seguinte arquivo de projeto Sdk:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Para obter mais informações, confira SDKs de Projeto do .NET

Pacote NuGet

Um aplicativo baseado no modelo de trabalho usa o SDK Microsoft.NET.Sdk.Worker e tem uma referência de pacote explícita ao pacote Microsoft.Extensions.Hosting.

Contêineres e adaptabilidade de nuvem

Com a maioria das cargas de trabalho modernas do .NET, os contêineres são uma opção viável. Ao criar um serviço de execução prolongada por meio do modelo de trabalho no Visual Studio, é possível aceitar o suporte do Docker. Fazer isso cria um Dockerfile que conteineriza seu aplicativo .NET. Um Dockerfile é um conjunto de instruções para compilar uma imagem. Para aplicativos .NET, o Dockerfile geralmente fica na raiz do diretório ao lado de um arquivo de solução.

# See https://aka.ms/containerfastmode to understand how Visual Studio uses this
# Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["background-service/App.WorkerService.csproj", "background-service/"]
RUN dotnet restore "background-service/App.WorkerService.csproj"
COPY . .
WORKDIR "/src/background-service"
RUN dotnet build "App.WorkerService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "App.WorkerService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "App.WorkerService.dll"]

As etapas anteriores do Dockerfile incluem:

  • Definir a imagem base de mcr.microsoft.com/dotnet/runtime:8.0 como o alias base.
  • Alterar o diretório de trabalho para /app.
  • Definir o alias build da imagem mcr.microsoft.com/dotnet/sdk:8.0.
  • Alterar o diretório de trabalho para /src.
  • Copiar conteúdos e publicar o aplicativo .NET:
  • Retransmitir a imagem do SDK do .NET por meio de mcr.microsoft.com/dotnet/runtime:8.0 (alias base).
  • Copiar a saída de compilação publicada de /publish.
  • Definir o ponto de entrada, que delega para dotnet App.BackgroundService.dll.

Dica

O MCR em mcr.microsoft.com significa "Registro de Contêiner da Microsoft" e é o catálogo de contêineres sindicalizado da Microsoft do hub oficial do Docker. O artigo do catálogo de contêineres dos sindicatos da Microsoft contém detalhes adicionais.

Quando você direciona o Docker como uma estratégia de implantação para o Serviço de Trabalho do .NET, há algumas considerações no arquivo de projeto:

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WorkerService</RootNamespace>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.5" />
  </ItemGroup>
</Project>

No arquivo de projeto anterior, o elemento <DockerDefaultTargetOS> especifica Linux como seu destino. Para direcionar contêineres do Windows, use Windows. O pacote NuGet Microsoft.VisualStudio.Azure.Containers.Tools.Targets é adicionado automaticamente como uma referência de pacote quando o suporte do Docker é selecionado no modelo.

Para obter mais informações sobre o Docker com o .NET, confira Tutorial: colocar em contêiner um aplicativo .NET. Para obter mais informações sobre como implantar no Azure, confira Tutorial: implantar um serviço de trabalho no Azure.

Importante

Para utilizar os segredos do usuário com o modelo de trabalho, é preciso fazer uma referência explícita ao pacote NuGet Microsoft.Extensions.Configuration.UserSecrets.

Extensibilidade do Serviço Hospedado

A interface IHostedService define dois métodos:

Esses dois métodos servem como métodos de ciclo de vida – são chamados durante eventos de início e parada do host, respectivamente.

Observação

Ao substituir os métodos StartAsync ou StopAsync, você deve chamar e await o método de classe base para garantir que o serviço seja iniciado e/ou desligado corretamente.

Importante

A interface serve como uma restrição de parâmetro de tipo genérico no método de extensão, o AddHostedService<THostedService>(IServiceCollection) que significa que apenas implementações são permitidas. Você é livre para usar o BackgroundService fornecido com uma subclasse ou implementar o seu do zero.

Sinalizar conclusão

Na maioria dos cenários comuns, você não precisa sinalizar explicitamente a conclusão de um serviço hospedado. Os serviços são projetados para serem executados desde quando o host os inicia até que o host seja interrompido. No entanto, em alguns cenários, pode ser necessário sinalizar a conclusão de todo o aplicativo host quando o serviço é concluído. Para sinalizar a conclusão, considere a seguinte classe Worker:

namespace App.SignalCompletionService;

public sealed class Worker(
    IHostApplicationLifetime hostApplicationLifetime,
    ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // TODO: implement single execution logic here.
        logger.LogInformation(
            "Worker running at: {Time}", DateTimeOffset.Now);

        await Task.Delay(1_000, stoppingToken);

        // When completed, the entire app host will stop.
        hostApplicationLifetime.StopApplication();
    }
}

No código anterior, o método ExecuteAsync não faz loop e, quando concluído, chama IHostApplicationLifetime.StopApplication().

Importante

Isso sinalizará ao host que ele deve parar e, sem essa chamada para StopApplication, o host continuará sendo executado indefinidamente.

Para obter mais informações, consulte:

Confira também