Partilhar via


Serviços de trabalho no .NET

Existem inúmeras razões para a criação de serviços de longa duração, tais como:

  • Processamento de dados com uso intensivo de CPU.
  • Colocação em fila de itens de trabalho no segundo plano.
  • Executar uma operação baseada no tempo em um cronograma.

O processamento de serviço em segundo plano geralmente não envolve uma interface do usuário (UI), mas as interfaces do usuário podem ser criadas em torno delas. Nos primórdios do .NET Framework, os desenvolvedores do Windows podiam criar os Serviços do Windows para esses fins. Agora com o .NET, você pode usar o BackgroundService, que é uma implementação do IHostedService, ou implementar o seu próprio.

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

Importante

A instalação do 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 trabalhador usando o comando dotnet new worker. 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 erroneamente usados como sinônimos. Esta seção define alguns desses termos para tornar sua intenção neste artigo mais aparente.

  • Serviço em segundo plano: O tipo BackgroundService.
  • Hosted Service: Implementações de IHostedService ou do próprio IHostedService.
  • Serviço de longa duração: qualquer serviço executado continuamente.
  • Windows Service: A infraestrutura do Windows Service, originalmente centrada no .NET Framework, mas agora acessível via .NET.
  • Worker Service: O modelo Worker Service.

Modelo de Serviço do Trabalhador

O modelo Worker Service está disponível na CLI do .NET e no Visual Studio. Para obter mais informações, consulte .NET CLI, dotnet new worker - template. 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:

Configurações padrão de modelo

O modelo Worker não habilita a coleta de lixo do servidor (GC) por padrão, pois há vários fatores que desempenham um papel na determinação de sua necessidade. Todos os cenários que exigem serviços de execução prolongada devem considerar as implicações de desempenho desta configuração 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

Ativado 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 um desempenho estável em serviços de longa duração, gerenciando a memória por longos períodos. Eficiência de recursos: pode conservar recursos de CPU e memória em ambientes com recursos limitados.
Manutenção reduzida: Minimiza a necessidade de gerenciamento manual de memória, simplificando a manutenção. Controle manual de memória: Fornece controle refinado sobre a memória para aplicações especializadas.
Comportamento previsível: contribui para um comportamento consistente e previsível do aplicativo. Adequado para processos de curta duração: Minimiza a sobrecarga da coleta de lixo para processos efêmeros ou de curta duração.

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 operária

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 faz loops uma vez por segundo, registrando a data e a hora atuais até que o processo seja sinalizado para cancelamento.

O arquivo de projeto

O template de trabalhador depende do seguinte ficheiro de projeto Sdk:

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

Para mais informações, consulte os SDKs de projetos .NET .

Pacote NuGet

Uma aplicação baseada no modelo Worker usa o SDK do Microsoft.NET.Sdk.Worker e tem uma referência explícita ao pacote Microsoft.Extensions.Hosting .

Contentores e adaptabilidade à nuvem

Com a maioria das cargas de trabalho .NET modernas, os contêineres são uma opção viável. Ao criar um serviço de longa execução a partir do modelo Worker no Visual Studio, pode-se optar por suporte ao Docker. Isso cria um Dockerfile que conteineriza seu aplicativo .NET. UmDockerfileé um conjunto de instruções para criar 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@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 do mcr.microsoft.com/dotnet/runtime:8.0 como o alias base.
  • Alterando o diretório de trabalho para /app.
  • Definindo o alias build a partir 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:
  • Retransmissão da imagem do SDK do .NET a partir do mcr.microsoft.com/dotnet/runtime:8.0 (o alias base).
  • Copiando a saída de compilação publicada do /publish.
  • Definir o ponto de entrada, que delega a dotnet App.BackgroundService.dll.

Dica

O MCR em mcr.microsoft.com significa "Microsoft Container Registry", e é o catálogo de contêineres sindicados da Microsoft a partir do hub oficial do Docker. O artigo do catálogo de contentores de sindicatos da Microsoft contém detalhes adicionais.

Quando você direciona o Docker como uma estratégia de implantação para seu .NET Worker Service, 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.7" />
    <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 em vez disso. O pacote NuGet Microsoft.VisualStudio.Azure.Containers.Tools.Targets é adicionado automaticamente como uma referência de pacote quando o suporte a Docker é selecionado a partir do modelo.

Para obter mais informações sobre o Docker com .NET, consulte Tutorial: Containerize a .NET app. Para obter mais informações sobre como implantar no Azure, consulte Tutorial: Implantar um serviço de trabalho no Azure.

Importante

Se quiser utilizar Segredos de Utilizador com o modelo Worker, será necessário fazer 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 do ciclo de vida - estes são invocados durante os eventos de arranque e paragem do host, respectivamente.

Observação

Ao substituir os métodos StartAsync ou StopAsync, deve-se chamar o método de classe await e 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 apenas implementações são permitidas. Você é livre para usar o BackgroundService fornecido com uma subclasse ou implementar completamente o seu próprio.

Sinalizar conclusão

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 é concluído, chama IHostApplicationLifetime.StopApplication().

Importante

Isso sinalizará para o host que ele deve parar, e sem essa chamada para StopApplication o host continuará a ser executado indefinidamente. Se pretende executar um serviço hospedado de curta duração (cenário de execução única) e deseja usar o modelo de Worker, deve chamar StopApplication para sinalizar ao host para parar.

Para mais informações, consulte:

Abordagem alternativa

Para um aplicativo de curta duração que necessita de injeção de dependências, registo em log e configuração, utilize o Host Genérico do .NET em vez do template do 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.10" />
  </ItemGroup>
</Project>

Sua Program classe pode ser algo como o seguinte:

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 ser executado. O RunAsync método é chamado no JobRunner, e se for concluído com êxito, o aplicativo retornará 0. Se ocorrer uma exceção não tratada, ele registra o erro e retorna 1.

Neste cenário simples, a JobRunner classe poderia 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.");
    }
}

Você obviamente 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.

Ver também