Usar serviços com escopo em um BackgroundService

Quando você registra implementações de IHostedService usando qualquer um dos métodos de extensão AddHostedService, o serviço é registrado como um singleton. Pode haver cenários em que você gostaria de contar com um serviço com escopo. Para obter mais informações, consulte Injeção de dependência no .NET: tempos de vida do serviço.

Neste tutorial, você aprenderá a:

Dica

Todo o código-fonte do exemplo dos “Trabalhos no .NET” está disponível no Navegador de Exemplos para download. Para obter mais informações, confira Procurar exemplos de código: Trabalhos no .NET.

Pré-requisitos

Criar um novo projeto

Para criar um projeto do Serviço de Trabalho com o Visual Studio, selecione Arquivo>Novo>Projeto.... Na caixa de diálogo Criar um projeto, pesquise "Serviço de Trabalho" e selecione o modelo de Serviço de Trabalho. Se preferir usar a CLI do .NET, abra seu terminal favorito em um diretório de trabalho. Execute o comando dotnet new e substitua <Project.Name> pelo nome do projeto desejado.

dotnet new worker --name <Project.Name>

Para obter mais informações sobre o novo comando do projeto de serviço de trabalho da CLI do .NET, confira dotnet new worker.

Dica

Se você estiver usando o Visual Studio Code, poderá executar comandos da CLI do .NET no terminal integrado. Para obter mais informações, consulte Visual Studio Code: Terminal Integrado.

Criar serviços com escopo

Para usar os serviços com escopo dentro de um BackgroundService, crie um escopo. Por padrão, nenhum escopo é criado para um serviço hospedado. O serviço em segundo plano com escopo contém a lógica da tarefa em segundo plano.

namespace App.ScopedService;

public interface IScopedProcessingService
{
    Task DoWorkAsync(CancellationToken stoppingToken);
}

A interface anterior define um único método DoWorkAsync. Para definir a implementação padrão:

  • O serviço é assíncrono. O método DoWorkAsync retorna uma Task. Para fins de demonstração, um atraso de dez segundos é aguardado no método DoWorkAsync.
  • Um ILogger é injetado no serviço.:
namespace App.ScopedService;

public sealed class DefaultScopedProcessingService(
    ILogger<DefaultScopedProcessingService> logger) : IScopedProcessingService
{
    private int _executionCount;

    public async Task DoWorkAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            ++ _executionCount;

            logger.LogInformation(
                "{ServiceName} working, execution count: {Count}",
                nameof(DefaultScopedProcessingService),
                _executionCount);

            await Task.Delay(10_000, stoppingToken);
        }
    }
}

O serviço hospedado cria um escopo para resolver o serviço em segundo plano com escopo para chamar seu método DoWorkAsync. DoWorkAsync retorna um Task, que é aguardado em ExecuteAsync:

Reescrever a classe Worker

Substitua a classe existente Worker pelo seguinte código C# e renomeie o arquivo para ScopedBackgroundService.cs:

namespace App.ScopedService;

public sealed class ScopedBackgroundService(
    IServiceScopeFactory serviceScopeFactory,
    ILogger<ScopedBackgroundService> logger) : BackgroundService
{
    private const string ClassName = nameof(ScopedBackgroundService);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation(
            "{Name} is running.", ClassName);

        await DoWorkAsync(stoppingToken);
    }

    private async Task DoWorkAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation(
            "{Name} is working.", ClassName);

        using (IServiceScope scope = serviceScopeFactory.CreateScope())
        {
            IScopedProcessingService scopedProcessingService =
                scope.ServiceProvider.GetRequiredService<IScopedProcessingService>();

            await scopedProcessingService.DoWorkAsync(stoppingToken);
        }
    }

    public override async Task StopAsync(CancellationToken stoppingToken)
    {
        logger.LogInformation(
            "{Name} is stopping.", ClassName);

        await base.StopAsync(stoppingToken);
    }
}

No código anterior, um escopo explícito é criado e a implementação IScopedProcessingService é resolvida do alocador de escopo do serviço de injeção de dependência. A instância de serviço resolvida tem o escopo definido e seu método DoWorkAsync é aguardado.

Substitua o conteúdo do arquivo de modelo Program.cs pelo código C# a seguir:

using App.ScopedService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<ScopedBackgroundService>();
builder.Services.AddScoped<IScopedProcessingService, DefaultScopedProcessingService>();

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

Os serviços são registrados em (Program.cs). O serviço hospedado está registrado com o método de extensão AddHostedService.

Para obter mais informações para registrar serviços, consulte Injeção de dependência no .NET.

Verificar a funcionalidade do serviço

Para executar o aplicativo no Visual Studio, selecione F5 ou a opção do menu Depuração>Iniciar Depuração. Se você estiver usando a CLI do .NET, execute o comando dotnet run no diretório de trabalho:

dotnet run

Para obter mais informações sobre o comando de execução da CLI do .NET, confira a execução do dotnet.

Deixe que o aplicativo seja executado por um bit para gerar vários incrementos de contagem de execução. Você verá saídas semelhantes às seguintes:

info: App.ScopedService.ScopedBackgroundService[0]
      ScopedBackgroundService is running.
info: App.ScopedService.ScopedBackgroundService[0]
      ScopedBackgroundService is working.
info: App.ScopedService.DefaultScopedProcessingService[0]
      DefaultScopedProcessingService working, execution count: 1
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: .\scoped-service
info: App.ScopedService.DefaultScopedProcessingService[0]
      DefaultScopedProcessingService working, execution count: 2
info: App.ScopedService.DefaultScopedProcessingService[0]
      DefaultScopedProcessingService working, execution count: 3
info: App.ScopedService.DefaultScopedProcessingService[0]
      DefaultScopedProcessingService working, execution count: 4
info: Microsoft.Hosting.Lifetime[0]
      Application is shutting down...
info: App.ScopedService.ScopedBackgroundService[0]
      ScopedBackgroundService is stopping.

Se estiver executando o aplicativo no Visual Studio, selecione Depurar>Parar Depuração.... Como alternativa, selecione Ctrl + C na janela do console para sinalizar o cancelamento.

Confira também