Compartilhar via


Serviços de trabalho no .NET

Há inúmeros motivos para criar serviços de longa duração, 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 primeiros dias com o .NET Framework, os desenvolvedores do Windows poderiam criar serviços windows para essas finalidades. Agora com o .NET, você pode usar o BackgroundService, que é uma implementação de IHostedService, ou implementar o seu próprio.

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). Elas fazem parte do conjunto de extensões de bibliotecas, o que as torna fundamentais para todas as cargas de trabalho do .NET que utilizam 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 novo worker usando o comando dotnet new worker. Se você estiver usando o Visual Studio, o modelo ficará oculto até que o ASP.NET opcional e a carga de trabalho de desenvolvimento da Web sejam instalados.

Terminologia

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

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

Modelo do Serviço de Trabalho

O modelo do 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>

Vantagens e desvantagens e considerações

Habilitado Desabilitado
Gerenciamento eficiente de memória: recupera automaticamente a memória não utilizado 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, consulte Server GC. Para obter mais informações sobre como configurar o GC do servidor, consulte 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 Worker anterior é 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, o ExecuteAsync executa um ciclo uma vez por segundo, registrando a data e a hora atuais até que o processo seja sinalizado para ser cancelado.

O arquivo de projeto

O modelo de trabalhador 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. Isso cria um Dockerfile que conteineriza seu aplicativo .NET. Um Dockerfile é um conjunto de instruções para criar uma imagem. Para aplicativos .NET, o Dockerfile geralmente está localizado na raiz do diretório ao lado de um arquivo de soluções.

# 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@sha256:e6b552fd7a0302e4db30661b16537f7efcdc0b67790a47dbf67a5e798582d3a5 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 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:

  • Definindo a imagem base de mcr.microsoft.com/dotnet/runtime:8.0 como o alias base.
  • Alterando o diretório de trabalho para /app.
  • Definir o alias build da imagem mcr.microsoft.com/dotnet/sdk:8.0.
  • Alterando o diretório de trabalho para /src.
  • Copiando o conteúdo e publicando 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.
  • Definindo o ponto de entrada, que atribui 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 integrado 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="9.0.6" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
  </ItemGroup>
</Project>

No arquivo de projeto anterior, o elemento <DockerDefaultTargetOS> especifica Linux como seu destino. Para direcionar contêineres do Windows, use Windows. O Microsoft.VisualStudio.Azure.Containers.Tools.Targets Pacote NuGet é 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, consulte Tutorial: Conteinerizar um aplicativo .NET. Para obter mais informações sobre como implantar no Azure, consulte 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.

Nota

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 AddHostedService<THostedService>(IServiceCollection), o que significa que somente implementações são permitidas. Você é livre para usar a BackgroundService fornecida com uma subclasse ou implementar totalmente a sua própria.

Finalização do sinal

Na maioria dos cenários comuns, você não precisa sinalizar explicitamente a conclusão de um serviço hospedado. Quando o host inicia os serviços, eles são projetados para serem executados até que o host seja interrompido. Em alguns cenários, no entanto, talvez seja necessário sinalizar a conclusão de todo o aplicativo host quando o serviço for 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 BackgroundService.ExecuteAsync(CancellationToken) não faz loop e, quando está 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. Se você pretende executar um serviço hospedado de curta duração (cenário de execução única) e quiser usar o Worker template, deverá chamar StopApplication para sinalizar ao host que deve parar.

Para obter mais informações, consulte:

Abordagem alternativa

Para um aplicativo temporário que precisa de injeção de dependência, registro em log e configuração, use o Host Genérico do .NET em vez do modelo Worker. Isso permite que você use esses recursos sem a Worker classe. Um exemplo simples de um aplicativo de curta duração usando o host genérico pode definir um arquivo de projeto como o seguinte:

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

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ShortLived.App</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
  </ItemGroup>
</Project>

Sua Program classe pode se parecer com o exemplo a seguir:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<JobRunner>();

using var host = builder.Build();

try
{
    var runner = host.Services.GetRequiredService<JobRunner>();

    await runner.RunAsync();

    return 0; // success
}
catch (Exception ex)
{
    var logger = host.Services.GetRequiredService<ILogger<Program>>();
    
    logger.LogError(ex, "Unhandled exception occurred during job execution.");

    return 1; // failure
}

O código anterior cria um JobRunner serviço, que é uma classe personalizada que contém a lógica para o trabalho a ser executado. O RunAsync método é chamado no JobRunnere, se for concluído com êxito, o aplicativo retornará 0. Se ocorrer uma exceção sem tratamento, ela registrará o erro e retornará 1.

Nesse cenário simples, a JobRunner classe pode ter esta aparência:

using Microsoft.Extensions.Logging;

internal sealed class JobRunner(ILogger<JobRunner> logger)
{
    public async Task RunAsync()
    {
        logger.LogInformation("Starting job...");

        // Simulate work
        await Task.Delay(1000);

        // Simulate failure
        // throw new InvalidOperationException("Something went wrong!");

        logger.LogInformation("Job completed successfully.");
    }
}

Obviamente, você precisaria adicionar lógica real ao RunAsync método, mas este exemplo demonstra como usar o host genérico para um aplicativo de curta duração sem a necessidade de uma Worker classe e sem a necessidade de sinalizar explicitamente a conclusão do host.

Consulte também