Implementar a interface IHostedService
Quando você precisa de controle finito além do BackgroundService fornecido, você pode implementar um IHostedService próprio. A interface IHostedService é a base para todos os serviços de execução prolongada no .NET. As implementações personalizadas são registradas com o método de extensão AddHostedService<THostedService>(IServiceCollection).
Neste tutorial, você aprenderá a:
- Implemente as interfaces IHostedService e IAsyncDisposable.
- Crie um serviço baseado em temporizador.
- Registre a implementação personalizada com injeção de dependência e registro em log.
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
- O SDK do .NET 8.0 ou posterior
- Um ambiente de desenvolvimento integrado do .NET (IDE)
- Fique à vontade para usar o Visual Studio
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, confira Visual Studio Code: Terminal Integrado.
Criar serviço de temporizador
O serviço em segundo plano baseado em temporizador faz uso da classe System.Threading.Timer. O temporizador dispara o método DoWork
. O temporizador é desabilitado em IHostLifetime.StopAsync(CancellationToken) e descartado quando o contêiner de serviço é descartado em IAsyncDisposable.DisposeAsync():
Substitua o conteúdo de Worker
do modelo pelo seguinte código C# e renomeie o arquivo para TimerService.cs:
namespace App.TimerHostedService;
public sealed class TimerService(ILogger<TimerService> logger) : IHostedService, IAsyncDisposable
{
private readonly Task _completedTask = Task.CompletedTask;
private int _executionCount = 0;
private Timer? _timer;
public Task StartAsync(CancellationToken stoppingToken)
{
logger.LogInformation("{Service} is running.", nameof(TimerHostedService));
_timer = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return _completedTask;
}
private void DoWork(object? state)
{
int count = Interlocked.Increment(ref _executionCount);
logger.LogInformation(
"{Service} is working, execution count: {Count:#,0}",
nameof(TimerHostedService),
count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
"{Service} is stopping.", nameof(TimerHostedService));
_timer?.Change(Timeout.Infinite, 0);
return _completedTask;
}
public async ValueTask DisposeAsync()
{
if (_timer is IAsyncDisposable timer)
{
await timer.DisposeAsync();
}
_timer = null;
}
}
Importante
Worker
era uma subclasse de BackgroundService. Agora, TimerService
implementa as interfaces IHostedService e IAsyncDisposable.
O TimerService
é sealed
e exibe em cascata a chamada DisposeAsync
da instância _timer
. Para obter mais informações sobre o "padrão de descarte em cascata", confira Implementar um método DisposeAsync
.
Quando StartAsync é chamado, o temporizador é instanciado, iniciando assim o temporizador.
Dica
O Timer não aguarda a conclusão das execuções anteriores de DoWork
, portanto, a abordagem mostrada pode não ser adequada para todos os cenários. Interlocked.Increment é usado para incrementar o contador de execução como uma operação atômica, o que garante que vários threads não atualizem _executionCount
simultaneamente.
Substitua o conteúdo Program
existente pelo seguinte código C#:
using App.TimerHostedService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<TimerService>();
IHost host = builder.Build();
host.Run();
O serviço é registrado em (Program.cs) com o método de extensão AddHostedService
. Esse é o mesmo método de extensão que você usa ao registrar as subclasses BackgroundService, pois ambas implementam a interface IHostedService.
Para obter mais informações para registrar serviços, confira 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.TimerHostedService.TimerService[0]
TimerHostedService is running.
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: .\timer-service
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 1
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 2
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 3
info: App.TimerHostedService.TimerService[0]
TimerHostedService is working, execution count: 4
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: App.TimerHostedService.TimerService[0]
TimerHostedService 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
Há vários tutoriais relacionados a considerar: