Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Por Jeow Li Huan
Note
Esta não é a versão mais recente deste artigo. Para a versão atual, consulte a versão .NET 10 deste artigo.
Warning
Esta versão do ASP.NET Core não é mais suportada. Para obter mais informações, consulte a Política de suporte do .NET e do .NET Core. Para a versão atual, consulte a versão .NET 10 deste artigo.
No ASP.NET Core, as tarefas em segundo plano podem ser implementadas como serviços hospedados. Um serviço hospedado é uma classe com lógica de tarefa em segundo plano que implementa a IHostedService interface. Este artigo fornece três exemplos de serviço hospedado:
- Tarefa em segundo plano executada em um temporizador.
- Serviço hospedado que ativa um serviço com escopo. O serviço definido pode usar injeção de dependência (DI).
- Tarefas em segundo plano enfileiradas que são executadas sequencialmente.
Modelo de Serviço do Trabalhador
O modelo ASP.NET Core Worker Service fornece um ponto de partida para escrever aplicativos de serviço de longa execução. Um aplicativo criado a partir do modelo Serviço de Trabalho especifica o SDK do Trabalhador em seu arquivo de projeto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para usar o modelo como base para um aplicativo de serviços hospedados:
- Crie um novo projeto.
- Selecione Serviço de Trabalhador. Selecione Avançar.
- Forneça um nome de projeto no campo Nome do projeto ou aceite o nome do projeto padrão. Selecione Avançar.
- Na caixa de diálogo Informações adicionais , escolha uma estrutura. Selecione Criar.
Package
Uma aplicação baseada no modelo de Serviço de Trabalhador utiliza o Microsoft.NET.Sdk.Worker SDK e tem uma referência explícita ao pacote Microsoft.Extensions.Hosting. Por exemplo, consulte o arquivo de projeto do aplicativo de exemplo (BackgroundTasksSample.csproj).
Para aplicativos Web que usam o Microsoft.NET.Sdk.Web SDK, o pacote Microsoft.Extensions.Hosting é referenciado implicitamente a partir da estrutura compartilhada. Não é necessária uma referência explícita de pacote no arquivo de projeto do aplicativo.
Interface IHostedService
A IHostedService interface define dois métodos para objetos que são gerenciados pelo host:
StartAsync
StartAsync(CancellationToken) contém a lógica para iniciar a tarefa em segundo plano.
StartAsync é chamado antes:
- O pipeline de processamento de solicitações do aplicativo está configurado.
- O servidor é iniciado e IApplicationLifetime.ApplicationStarted é acionado.
StartAsync deve ser limitado a tarefas de execução curta porque os serviços hospedados são executados sequencialmente e nenhum outro serviço é iniciado até StartAsync ser concluído.
StopAsync
-
StopAsync(CancellationToken) é acionado quando o host está executando um desligamento gracioso.
StopAsyncContém a lógica para finalizar a tarefa em segundo plano. Implementar IDisposable e finalizadores (destruidores) para eliminar quaisquer recursos não gerenciados.
O token de cancelamento tem um limite de tempo padrão de 30 segundos para indicar que o processo de desligamento não deve mais ser suave. Quando o cancelamento é solicitado no token:
- Todas as operações em segundo plano restantes que o aplicativo está executando devem ser anuladas.
- Os métodos chamados em
StopAsyncdevem retornar imediatamente.
No entanto, as tarefas não são abandonadas após o cancelamento ser solicitado — o chamador aguarda que todas as tarefas sejam concluídas.
Se a aplicação encerrar inesperadamente (por exemplo, se o processo da aplicação falhar), StopAsync pode não ser chamado. Portanto, quaisquer métodos chamados ou operações realizadas em StopAsync podem não ocorrer.
Para estender o tempo limite de desligamento padrão de 30 segundos, defina:
- ShutdownTimeout ao usar o Host Genérico. Para mais informações, consulte o Host Genérico do .NET no ASP.NET Core.
- Definição de configuração do host de tempo limite de desligamento ao usar o host da Web. Para obter mais informações, consulte ASP.NET Core Web Host.
O serviço hospedado é ativado uma vez na inicialização do aplicativo e normalmente desligado no desligamento do aplicativo. Se um erro for gerado durante a execução da tarefa em segundo plano, Dispose deve ser chamado mesmo que StopAsync não seja chamado.
Classe base BackgroundService
BackgroundService é uma classe base para implementar um IHostedService de longa duração.
ExecuteAsync(CancellationToken) é chamado para executar o serviço em segundo plano. A implementação retorna um Task que representa todo o tempo de vida do serviço em segundo plano. Nenhum outro serviço é iniciado até que o ExecuteAsync se torne assíncrono, como chamando await. Evite realizar trabalho de inicialização longo e bloqueante no ExecuteAsync. O host bloqueia em StopAsync(CancellationToken) aguardando ExecuteAsync para concluir.
O token de cancelamento é acionado quando IHostedService.StopAsync é chamado. A sua implementação de ExecuteAsync deve terminar imediatamente quando o token de cancelamento for ativado, a fim de desligar o serviço graciosamente. Caso contrário, o serviço desliga-se ingraciosamente no tempo limite de encerramento. Para obter mais informações, consulte a seção Interface IHostedService .
Para obter mais informações, consulte o código-fonte do BackgroundService .
Tarefas cronometradas em segundo plano
Uma tarefa em segundo plano cronometrada usa a classe System.Threading.Timer . O temporizador aciona o método DoWork da tarefa. O temporizador é desativado em StopAsync e descartado quando o contentor de serviço é descartado em Dispose:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
O Timer não espera que as execuções de DoWork anteriores terminem, 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 sejam atualizados executionCount simultaneamente.
O serviço está registado em IHostBuilder.ConfigureServices (Program.cs) com o método de extensão AddHostedService.
services.AddHostedService<TimedHostedService>();
Consumindo um serviço sob escopo numa tarefa em segundo plano
Para usar serviços com escopo em um BackgroundService, crie um escopo. Nenhum escopo é criado para um serviço hospedado por padrão.
O serviço com escopo da tarefa em segundo plano contém a lógica da tarefa em segundo plano. No exemplo a seguir:
- O serviço é assíncrono. O
DoWorkmétodo retorna umTaskarquivo . Para fins de demonstração, espera-se um atraso de dez segundos noDoWorkmétodo. - Um ILogger é injetado no serviço.
internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
O serviço hospedado cria um escopo para resolver o serviço com escopo da tarefa em segundo plano, com o objetivo de chamar o seu método DoWork.
DoWork retorna um Task, que é aguardado em ExecuteAsync.
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Os serviços estão registados em IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado é registado com o método AddHostedService de extensão:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tarefas em segundo plano enfileiradas
Uma fila de tarefas em segundo plano é baseada no .NET Framework 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
No exemplo QueueHostedService seguinte:
- O
BackgroundProcessingmétodo retorna umTask, que é aguardado emExecuteAsync. - As tarefas em segundo plano na fila são retiradas da fila e executadas no
BackgroundProcessing. - Os itens de trabalho estão à espera de serem completados antes que o serviço pare no
StopAsync.
public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Um MonitorLoop serviço lida com tarefas de enfileiramento para o serviço hospedado sempre que a w chave é selecionada em um dispositivo de entrada:
- O
IBackgroundTaskQueueé injetado no serviçoMonitorLoop. -
IBackgroundTaskQueue.QueueBackgroundWorkItemé invocado para enfileirar um item de trabalho. - O item de trabalho simula uma tarefa em segundo plano de longa duração:
- Três atrasos de 5 segundos são executados (
Task.Delay). - Uma
try-catchinstrução interceta OperationCanceledException se a tarefa for cancelada.
- Três atrasos de 5 segundos são executados (
public class MonitorLoop
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void StartMonitorLoop()
{
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. "
+ "{DelayLoop}/3", guid, delayLoop);
}
if (delayLoop == 3)
{
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Os serviços estão registados em IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado é registado com o método AddHostedService de extensão:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop é iniciado em Program.cs:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Tarefa em segundo plano cronometrada assíncrona
O código a seguir cria uma tarefa em segundo plano cronometrada assíncrona:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
await DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
private async Task DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
// Simulate work
await Task.Delay(TimeSpan.FromSeconds(2));
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
AOT nativo
Os modelos do Serviço de Trabalho oferecem suporte à execução nativa do .NET ahead-of-time (AOT) com a flag --aot:
- Crie um novo projeto.
- Selecione Serviço de Trabalhador. Selecione Avançar.
- Forneça um nome de projeto no campo Nome do projeto ou aceite o nome do projeto padrão. Selecione Avançar.
- Na caixa de diálogo Informações adicionais :
- Escolha uma estrutura.
- Marque a caixa de seleção Ativar publicação AOT nativa .
- Selecione Criar.
A opção AOT adiciona <PublishAot>true</PublishAot> ao arquivo de projeto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
+ <PublishAot>true</PublishAot>
<UserSecretsId>dotnet-WorkerWithAot-e94b2</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0-preview.4.23259.5" />
</ItemGroup>
</Project>
Recursos adicionais
- Testes de unidade de serviços em segundo plano no GitHub.
- Visualizar ou descarregar amostra de código (como descarregar)
- Implementar tarefas em segundo plano em microsserviços com IHostedService e a classe BackgroundService
- Executar tarefas em segundo plano com WebJobs no Serviço de Aplicativo do Azure
- Timer
No ASP.NET Core, as tarefas em segundo plano podem ser implementadas como serviços hospedados. Um serviço hospedado é uma classe com lógica de tarefa em segundo plano que implementa a IHostedService interface. Este artigo fornece três exemplos de serviço hospedado:
- Tarefa em segundo plano executada em um temporizador.
- Serviço hospedado que ativa um serviço com escopo. O serviço definido pode usar injeção de dependência (DI).
- Tarefas em segundo plano enfileiradas que são executadas sequencialmente.
Modelo de Serviço do Trabalhador
O modelo ASP.NET Core Worker Service fornece um ponto de partida para escrever aplicativos de serviço de longa execução. Um aplicativo criado a partir do modelo Serviço de Trabalho especifica o SDK do Trabalhador em seu arquivo de projeto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para usar o modelo como base para um aplicativo de serviços hospedados:
- Crie um novo projeto.
- Selecione Serviço de Trabalhador. Selecione Avançar.
- Forneça um nome de projeto no campo Nome do projeto ou aceite o nome do projeto padrão. Selecione Avançar.
- Na caixa de diálogo Informações adicionais , escolha uma estrutura. Selecione Criar.
Package
Uma aplicação baseada no modelo de Serviço de Trabalhador utiliza o Microsoft.NET.Sdk.Worker SDK e tem uma referência explícita ao pacote Microsoft.Extensions.Hosting. Por exemplo, consulte o arquivo de projeto do aplicativo de exemplo (BackgroundTasksSample.csproj).
Para aplicativos Web que usam o Microsoft.NET.Sdk.Web SDK, o pacote Microsoft.Extensions.Hosting é referenciado implicitamente a partir da estrutura compartilhada. Não é necessária uma referência explícita de pacote no arquivo de projeto do aplicativo.
Interface IHostedService
A IHostedService interface define dois métodos para objetos que são gerenciados pelo host:
StartAsync
StartAsync(CancellationToken) contém a lógica para iniciar a tarefa em segundo plano.
StartAsync é chamado antes:
- O pipeline de processamento de solicitações do aplicativo está configurado.
- O servidor é iniciado e IApplicationLifetime.ApplicationStarted é acionado.
StartAsync deve ser limitado a tarefas de execução curta porque os serviços hospedados são executados sequencialmente e nenhum outro serviço é iniciado até StartAsync ser concluído.
StopAsync
-
StopAsync(CancellationToken) é acionado quando o host está executando um desligamento gracioso.
StopAsyncContém a lógica para finalizar a tarefa em segundo plano. Implementar IDisposable e finalizadores (destruidores) para eliminar quaisquer recursos não gerenciados.
O token de cancelamento tem um limite de tempo padrão de 30 segundos para indicar que o processo de desligamento não deve mais ser suave. Quando o cancelamento é solicitado no token:
- Todas as operações em segundo plano restantes que o aplicativo está executando devem ser anuladas.
- Os métodos chamados em
StopAsyncdevem retornar imediatamente.
No entanto, as tarefas não são abandonadas após o cancelamento ser solicitado — o chamador aguarda que todas as tarefas sejam concluídas.
Se a aplicação encerrar inesperadamente (por exemplo, se o processo da aplicação falhar), StopAsync pode não ser chamado. Portanto, quaisquer métodos chamados ou operações realizadas em StopAsync podem não ocorrer.
Para estender o tempo limite de desligamento padrão de 30 segundos, defina:
- ShutdownTimeout ao usar o Host Genérico. Para mais informações, consulte o Host Genérico do .NET no ASP.NET Core.
- Definição de configuração do host de tempo limite de desligamento ao usar o host da Web. Para obter mais informações, consulte ASP.NET Core Web Host.
O serviço hospedado é ativado uma vez na inicialização do aplicativo e normalmente desligado no desligamento do aplicativo. Se um erro for gerado durante a execução da tarefa em segundo plano, Dispose deve ser chamado mesmo que StopAsync não seja chamado.
Classe base BackgroundService
BackgroundService é uma classe base para implementar um IHostedService de longa duração.
ExecuteAsync(CancellationToken) é chamado para executar o serviço em segundo plano. A implementação retorna um Task que representa todo o tempo de vida do serviço em segundo plano. Nenhum outro serviço é iniciado até que o ExecuteAsync se torne assíncrono, como chamando await. Evite realizar trabalho de inicialização longo e bloqueante no ExecuteAsync. O host bloqueia em StopAsync(CancellationToken) aguardando ExecuteAsync para concluir.
O token de cancelamento é acionado quando IHostedService.StopAsync é chamado. A sua implementação de ExecuteAsync deve terminar imediatamente quando o token de cancelamento for ativado, a fim de desligar o serviço graciosamente. Caso contrário, o serviço desliga-se ingraciosamente no tempo limite de encerramento. Para obter mais informações, consulte a seção Interface IHostedService .
Para obter mais informações, consulte o código-fonte do BackgroundService .
Tarefas cronometradas em segundo plano
Uma tarefa em segundo plano cronometrada usa a classe System.Threading.Timer . O temporizador aciona o método DoWork da tarefa. O temporizador é desativado em StopAsync e descartado quando o contentor de serviço é descartado em Dispose:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer? _timer = null;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object? state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
O Timer não espera que as execuções de DoWork anteriores terminem, 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 sejam atualizados executionCount simultaneamente.
O serviço está registado em IHostBuilder.ConfigureServices (Program.cs) com o método de extensão AddHostedService.
services.AddHostedService<TimedHostedService>();
Consumindo um serviço sob escopo numa tarefa em segundo plano
Para usar serviços com escopo em um BackgroundService, crie um escopo. Nenhum escopo é criado para um serviço hospedado por padrão.
O serviço com escopo da tarefa em segundo plano contém a lógica da tarefa em segundo plano. No exemplo a seguir:
- O serviço é assíncrono. O
DoWorkmétodo retorna umTaskarquivo . Para fins de demonstração, espera-se um atraso de dez segundos noDoWorkmétodo. - Um ILogger é injetado no serviço.
internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
O serviço hospedado cria um escopo para resolver o serviço com escopo da tarefa em segundo plano, com o objetivo de chamar o seu método DoWork.
DoWork retorna um Task, que é aguardado em ExecuteAsync.
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Os serviços estão registados em IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado é registado com o método AddHostedService de extensão:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tarefas em segundo plano enfileiradas
Uma fila de tarefas em segundo plano é baseada no .NET Framework 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
No exemplo QueueHostedService seguinte:
- O
BackgroundProcessingmétodo retorna umTask, que é aguardado emExecuteAsync. - As tarefas em segundo plano na fila são retiradas da fila e executadas no
BackgroundProcessing. - Os itens de trabalho estão à espera de serem completados antes que o serviço pare no
StopAsync.
public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Um MonitorLoop serviço lida com tarefas de enfileiramento para o serviço hospedado sempre que a w chave é selecionada em um dispositivo de entrada:
- O
IBackgroundTaskQueueé injetado no serviçoMonitorLoop. -
IBackgroundTaskQueue.QueueBackgroundWorkItemé invocado para enfileirar um item de trabalho. - O item de trabalho simula uma tarefa em segundo plano de longa duração:
- Três atrasos de 5 segundos são executados (
Task.Delay). - Uma
try-catchinstrução interceta OperationCanceledException se a tarefa for cancelada.
- Três atrasos de 5 segundos são executados (
public class MonitorLoop
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void StartMonitorLoop()
{
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. "
+ "{DelayLoop}/3", guid, delayLoop);
}
if (delayLoop == 3)
{
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Os serviços estão registados em IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado é registado com o método AddHostedService de extensão:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx =>
{
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop é iniciado em Program.cs:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Tarefa em segundo plano cronometrada assíncrona
O código a seguir cria uma tarefa em segundo plano cronometrada assíncrona:
namespace TimedBackgroundTasks;
public class TimedHostedService : BackgroundService
{
private readonly ILogger<TimedHostedService> _logger;
private int _executionCount;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
// When the timer should have no due-time, then do the work once now.
await DoWork();
using PeriodicTimer timer = new(TimeSpan.FromSeconds(30));
try
{
while (await timer.WaitForNextTickAsync(stoppingToken))
{
await DoWork();
}
}
catch (OperationCanceledException)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
}
}
private async Task DoWork()
{
int count = Interlocked.Increment(ref _executionCount);
// Simulate work
await Task.Delay(TimeSpan.FromSeconds(2));
_logger.LogInformation("Timed Hosted Service is working. Count: {Count}", count);
}
}
Recursos adicionais
- Testes de unidade de serviços em segundo plano no GitHub.
- Visualizar ou descarregar amostra de código (como descarregar)
- Implementar tarefas em segundo plano em microsserviços com IHostedService e a classe BackgroundService
- Executar tarefas em segundo plano com WebJobs no Serviço de Aplicativo do Azure
- Timer
No ASP.NET Core, as tarefas em segundo plano podem ser implementadas como serviços hospedados. Um serviço hospedado é uma classe com lógica de tarefa em segundo plano que implementa a IHostedService interface. Este artigo fornece três exemplos de serviço hospedado:
- Tarefa em segundo plano executada em um temporizador.
- Serviço hospedado que ativa um serviço com escopo. O serviço definido pode usar injeção de dependência (DI).
- Tarefas em segundo plano enfileiradas que são executadas sequencialmente.
Visualizar ou descarregar amostra de código (como descarregar)
Modelo de Serviço do Trabalhador
O modelo ASP.NET Core Worker Service fornece um ponto de partida para escrever aplicativos de serviço de longa execução. Um aplicativo criado a partir do modelo Serviço de Trabalho especifica o SDK do Trabalhador em seu arquivo de projeto:
<Project Sdk="Microsoft.NET.Sdk.Worker">
Para usar o modelo como base para um aplicativo de serviços hospedados:
- Crie um novo projeto.
- Selecione Serviço de Trabalhador. Selecione Avançar.
- Forneça um nome de projeto no campo Nome do projeto ou aceite o nome do projeto padrão. Selecione Criar.
- Na caixa de diálogo Criar um novo serviço de trabalho, selecione Criar.
Package
Uma aplicação baseada no modelo de Serviço de Trabalhador utiliza o Microsoft.NET.Sdk.Worker SDK e tem uma referência explícita ao pacote Microsoft.Extensions.Hosting. Por exemplo, consulte o arquivo de projeto do aplicativo de exemplo (BackgroundTasksSample.csproj).
Para aplicativos Web que usam o Microsoft.NET.Sdk.Web SDK, o pacote Microsoft.Extensions.Hosting é referenciado implicitamente a partir da estrutura compartilhada. Não é necessária uma referência explícita de pacote no arquivo de projeto do aplicativo.
Interface IHostedService
A IHostedService interface define dois métodos para objetos que são gerenciados pelo host:
StartAsync
StartAsync Contém a lógica para iniciar a tarefa em segundo plano.
StartAsync é chamado antes:
- O pipeline de processamento de solicitações do aplicativo está configurado.
- O servidor é iniciado e IApplicationLifetime.ApplicationStarted é acionado.
O comportamento padrão pode ser alterado para que o serviço hospedado StartAsync seja executado após o pipeline do aplicativo ter sido configurado e ApplicationStarted chamado. Para alterar o comportamento padrão, adicione o serviço hospedado (VideosWatcher no exemplo a seguir) depois de chamar ConfigureWebHostDefaults:
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.ConfigureServices(services =>
{
services.AddHostedService<VideosWatcher>();
});
}
StopAsync
-
StopAsync(CancellationToken) é acionado quando o host está executando um desligamento gracioso.
StopAsyncContém a lógica para finalizar a tarefa em segundo plano. Implementar IDisposable e finalizadores (destruidores) para eliminar quaisquer recursos não gerenciados.
O token de cancelamento tem um tempo limite padrão de cinco segundos para indicar que o processo de desligamento não deve mais ser suave. Quando o cancelamento é solicitado no token:
- Todas as operações em segundo plano restantes que o aplicativo está executando devem ser anuladas.
- Os métodos chamados em
StopAsyncdevem retornar imediatamente.
No entanto, as tarefas não são abandonadas após o cancelamento ser solicitado — o chamador aguarda que todas as tarefas sejam concluídas.
Se a aplicação encerrar inesperadamente (por exemplo, se o processo da aplicação falhar), StopAsync pode não ser chamado. Portanto, quaisquer métodos chamados ou operações realizadas em StopAsync podem não ocorrer.
Para estender o tempo limite de desligamento padrão de cinco segundos, defina:
- ShutdownTimeout ao usar o Host Genérico. Para mais informações, consulte o Host Genérico do .NET no ASP.NET Core.
- Definição de configuração do host de tempo limite de desligamento ao usar o host da Web. Para obter mais informações, consulte ASP.NET Core Web Host.
O serviço hospedado é ativado uma vez na inicialização do aplicativo e normalmente desligado no desligamento do aplicativo. Se um erro for gerado durante a execução da tarefa em segundo plano, Dispose deve ser chamado mesmo que StopAsync não seja chamado.
Classe base BackgroundService
BackgroundService é uma classe base para implementar um IHostedService de longa duração.
ExecuteAsync(CancellationToken) é chamado para executar o serviço em segundo plano. A implementação retorna um Task que representa todo o tempo de vida do serviço em segundo plano. Nenhum outro serviço é iniciado até que o ExecuteAsync se torne assíncrono, como chamando await. Evite realizar trabalho de inicialização longo e bloqueante no ExecuteAsync. O host bloqueia em StopAsync(CancellationToken) aguardando ExecuteAsync para concluir.
O token de cancelamento é acionado quando IHostedService.StopAsync é chamado. A sua implementação de ExecuteAsync deve terminar imediatamente quando o token de cancelamento for ativado, a fim de desligar o serviço graciosamente. Caso contrário, o serviço desliga-se ingraciosamente no tempo limite de encerramento. Para obter mais informações, consulte a seção Interface IHostedService .
StartAsync deve ser limitado a tarefas de execução curta porque os serviços hospedados são executados sequencialmente e nenhum outro serviço é iniciado até StartAsync ser concluído. Tarefas de longa execução devem ser colocadas em ExecuteAsync. Para obter mais informações, consulte a fonte para BackgroundService.
Tarefas cronometradas em segundo plano
Uma tarefa em segundo plano cronometrada usa a classe System.Threading.Timer . O temporizador aciona o método DoWork da tarefa. O temporizador é desativado em StopAsync e descartado quando o contentor de serviço é descartado em Dispose:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
O Timer não espera que as execuções de DoWork anteriores terminem, 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 sejam atualizados executionCount simultaneamente.
O serviço está registado em IHostBuilder.ConfigureServices (Program.cs) com o método de extensão AddHostedService.
services.AddHostedService<TimedHostedService>();
Consumindo um serviço sob escopo numa tarefa em segundo plano
Para usar serviços com escopo em um BackgroundService, crie um escopo. Nenhum escopo é criado para um serviço hospedado por padrão.
O serviço com escopo da tarefa em segundo plano contém a lógica da tarefa em segundo plano. No exemplo a seguir:
- O serviço é assíncrono. O
DoWorkmétodo retorna umTaskarquivo . Para fins de demonstração, espera-se um atraso de dez segundos noDoWorkmétodo. - Um ILogger é injetado no serviço.
internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService : IScopedProcessingService
{
private int executionCount = 0;
private readonly ILogger _logger;
public ScopedProcessingService(ILogger<ScopedProcessingService> logger)
{
_logger = logger;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
executionCount++;
_logger.LogInformation(
"Scoped Processing Service is working. Count: {Count}", executionCount);
await Task.Delay(10000, stoppingToken);
}
}
}
O serviço hospedado cria um escopo para resolver o serviço com escopo da tarefa em segundo plano, com o objetivo de chamar o seu método DoWork.
DoWork retorna um Task, que é aguardado em ExecuteAsync.
public class ConsumeScopedServiceHostedService : BackgroundService
{
private readonly ILogger<ConsumeScopedServiceHostedService> _logger;
public ConsumeScopedServiceHostedService(IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger)
{
Services = services;
_logger = logger;
}
public IServiceProvider Services { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service running.");
await DoWork(stoppingToken);
}
private async Task DoWork(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is working.");
using (var scope = Services.CreateScope())
{
var scopedProcessingService =
scope.ServiceProvider
.GetRequiredService<IScopedProcessingService>();
await scopedProcessingService.DoWork(stoppingToken);
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
"Consume Scoped Service Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Os serviços estão registados em IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado é registado com o método AddHostedService de extensão:
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tarefas em segundo plano enfileiradas
Uma fila de tarefas em segundo plano é baseada no .NET Framework 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public BackgroundTaskQueue(int capacity)
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait will cause calls to WriteAsync() to return a task,
// which completes only when space became available. This leads to backpressure,
// in case too many publishers/calls start accumulating.
var options = new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
if (workItem == null)
{
throw new ArgumentNullException(nameof(workItem));
}
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
var workItem = await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
No exemplo QueueHostedService seguinte:
- O
BackgroundProcessingmétodo retorna umTask, que é aguardado emExecuteAsync. - As tarefas em segundo plano na fila são retiradas da fila e executadas no
BackgroundProcessing. - Os itens de trabalho estão à espera de serem completados antes que o serviço pare no
StopAsync.
public class QueuedHostedService : BackgroundService
{
private readonly ILogger<QueuedHostedService> _logger;
public QueuedHostedService(IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger)
{
TaskQueue = taskQueue;
_logger = logger;
}
public IBackgroundTaskQueue TaskQueue { get; }
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation(
$"Queued Hosted Service is running.{Environment.NewLine}" +
$"{Environment.NewLine}Tap W to add a work item to the " +
$"background queue.{Environment.NewLine}");
await BackgroundProcessing(stoppingToken);
}
private async Task BackgroundProcessing(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var workItem =
await TaskQueue.DequeueAsync(stoppingToken);
try
{
await workItem(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex,
"Error occurred executing {WorkItem}.", nameof(workItem));
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queued Hosted Service is stopping.");
await base.StopAsync(stoppingToken);
}
}
Um MonitorLoop serviço lida com tarefas de enfileiramento para o serviço hospedado sempre que a w chave é selecionada em um dispositivo de entrada:
- O
IBackgroundTaskQueueé injetado no serviçoMonitorLoop. -
IBackgroundTaskQueue.QueueBackgroundWorkItemé invocado para enfileirar um item de trabalho. - O item de trabalho simula uma tarefa em segundo plano de longa duração:
- Três atrasos de 5 segundos são executados (
Task.Delay). - Uma
try-catchinstrução interceta OperationCanceledException se a tarefa for cancelada.
- Três atrasos de 5 segundos são executados (
public class MonitorLoop
{
private readonly IBackgroundTaskQueue _taskQueue;
private readonly ILogger _logger;
private readonly CancellationToken _cancellationToken;
public MonitorLoop(IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
_taskQueue = taskQueue;
_logger = logger;
_cancellationToken = applicationLifetime.ApplicationStopping;
}
public void StartMonitorLoop()
{
_logger.LogInformation("MonitorAsync Loop is starting.");
// Run a console user input loop in a background thread
Task.Run(async () => await MonitorAsync());
}
private async ValueTask MonitorAsync()
{
while (!_cancellationToken.IsCancellationRequested)
{
var keyStroke = Console.ReadKey();
if (keyStroke.Key == ConsoleKey.W)
{
// Enqueue a background work item
await _taskQueue.QueueBackgroundWorkItemAsync(BuildWorkItem);
}
}
}
private async ValueTask BuildWorkItem(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid().ToString();
_logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
while (!token.IsCancellationRequested && delayLoop < 3)
{
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
// Prevent throwing if the Delay is cancelled
}
delayLoop++;
_logger.LogInformation("Queued Background Task {Guid} is running. " + "{DelayLoop}/3", guid, delayLoop);
}
if (delayLoop == 3)
{
_logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
_logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Os serviços estão registados em IHostBuilder.ConfigureServices (Program.cs). O serviço hospedado é registado com o método AddHostedService de extensão:
services.AddSingleton<MonitorLoop>();
services.AddHostedService<QueuedHostedService>();
services.AddSingleton<IBackgroundTaskQueue>(ctx => {
if (!int.TryParse(hostContext.Configuration["QueueCapacity"], out var queueCapacity))
queueCapacity = 100;
return new BackgroundTaskQueue(queueCapacity);
});
MonitorLoop é iniciado em Program.Main:
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();