Criar um serviço de fila
Um serviço de fila é um ótimo exemplo de um serviço de longa execução, onde os itens de trabalho podem ser enfileirados e trabalhados sequencialmente à medida que os itens de trabalho anteriores são concluídos. Baseando-se no modelo de Serviço de Trabalho, você cria novas funcionalidades sobre o BackgroundService.
Neste tutorial, irá aprender a:
- Crie um serviço de fila.
- Delegar trabalho a uma fila de tarefas.
- Registre um ouvinte de teclas do console a partir de IHostApplicationLifetime eventos.
Gorjeta
Todo o código-fonte de exemplo "Workers in .NET" está disponível no Navegador de Amostras para download. Para obter mais informações, consulte Procurar exemplos de código: Trabalhadores no .NET.
Pré-requisitos
- O SDK do .NET 8.0 ou posterior
- Um ambiente de desenvolvimento integrado (IDE) .NET
- Sinta-se à vontade para usar o Visual Studio
Criar um novo projeto
Para criar um novo projeto do Serviço de Trabalho com o Visual Studio, selecione Arquivo>Novo>Projeto....Na caixa de diálogo Criar um novo projeto, procure por "Serviço de Trabalho" e selecione Modelo de Serviço de Trabalhador. Se você preferir usar a CLI do .NET, abra seu terminal favorito em um diretório de trabalho. Execute o comando e substitua o dotnet new
pelo nome do <Project.Name>
projeto desejado.
dotnet new worker --name <Project.Name>
Para obter mais informações sobre o comando .NET CLI new worker service project, consulte dotnet new worker.
Gorjeta
Se você estiver usando o Visual Studio Code, poderá executar comandos da CLI do .NET a partir do terminal integrado. Para obter mais informações, consulte Visual Studio Code: Integrated Terminal.
Criar serviços de fila
Você pode estar familiarizado com a QueueBackgroundWorkItem(Func<CancellationToken,Task>)System.Web.Hosting
funcionalidade do namespace.
Gorjeta
A funcionalidade do namespace não foi intencionalmente transferida para o System.Web
.NET e permanece exclusiva do .NET Framework. Para obter mais informações, consulte Introdução ao ASP.NET incremental para migração ASP.NET Core.
No .NET, para modelar um serviço inspirado na QueueBackgroundWorkItem
funcionalidade, comece adicionando uma IBackgroundTaskQueue
interface ao projeto:
namespace App.QueueService;
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
Há dois métodos, um que expõe a funcionalidade de enfileiramento e outro que desfila itens de trabalho enfileirados anteriormente. Um item de trabalho é um Func<CancellationToken, ValueTask>
arquivo . Em seguida, adicione a implementação padrão ao projeto.
using System.Threading.Channels;
namespace App.QueueService;
public sealed class DefaultBackgroundTaskQueue : IBackgroundTaskQueue
{
private readonly Channel<Func<CancellationToken, ValueTask>> _queue;
public DefaultBackgroundTaskQueue(int capacity)
{
BoundedChannelOptions options = new(capacity)
{
FullMode = BoundedChannelFullMode.Wait
};
_queue = Channel.CreateBounded<Func<CancellationToken, ValueTask>>(options);
}
public async ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem)
{
ArgumentNullException.ThrowIfNull(workItem);
await _queue.Writer.WriteAsync(workItem);
}
public async ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken)
{
Func<CancellationToken, ValueTask>? workItem =
await _queue.Reader.ReadAsync(cancellationToken);
return workItem;
}
}
A implementação anterior depende de um Channel<T> como uma fila. O BoundedChannelOptions(Int32) é chamado com uma capacidade explícita. A capacidade deve ser definida com base na carga esperada do aplicativo e no número de threads simultâneos que acessam a fila. BoundedChannelFullMode.Wait faz com que as chamadas retornem ChannelWriter<T>.WriteAsync uma tarefa, que é concluída somente quando o espaço fica disponível. O que leva a uma pressão negativa, no caso de muitos editores/chamadas começarem a acumular-se.
Reescrever a classe Worker
No exemplo a seguir QueueHostedService
:
- O
ProcessTaskQueueAsync
método retorna um Task emExecuteAsync
. - As tarefas em segundo plano na fila são retiradas da fila e executadas no
ProcessTaskQueueAsync
. - Os itens de trabalho são aguardados antes que o serviço pare no
StopAsync
.
Substitua a classe existente Worker
pelo seguinte código C# e renomeie o arquivo para QueueHostedService.cs.
namespace App.QueueService;
public sealed class QueuedHostedService(
IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger) : BackgroundService
{
protected override Task ExecuteAsync(CancellationToken stoppingToken)
{
logger.LogInformation("""
{Name} is running.
Tap W to add a work item to the
background queue.
""",
nameof(QueuedHostedService));
return ProcessTaskQueueAsync(stoppingToken);
}
private async Task ProcessTaskQueueAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
Func<CancellationToken, ValueTask>? workItem =
await taskQueue.DequeueAsync(stoppingToken);
await workItem(stoppingToken);
}
catch (OperationCanceledException)
{
// Prevent throwing if stoppingToken was signaled
}
catch (Exception ex)
{
logger.LogError(ex, "Error occurred executing task work item.");
}
}
}
public override async Task StopAsync(CancellationToken stoppingToken)
{
logger.LogInformation(
$"{nameof(QueuedHostedService)} 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
é injetadoMonitorLoop
no serviço. IBackgroundTaskQueue.QueueBackgroundWorkItemAsync
é chamado 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 Delay.
- Uma
try-catch
instrução interceta OperationCanceledException se a tarefa for cancelada.
namespace App.QueueService;
public sealed class MonitorLoop(
IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
private readonly CancellationToken _cancellationToken = applicationLifetime.ApplicationStopping;
public void StartMonitorLoop()
{
logger.LogInformation($"{nameof(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(BuildWorkItemAsync);
}
}
}
private async ValueTask BuildWorkItemAsync(CancellationToken token)
{
// Simulate three 5-second tasks to complete
// for each enqueued work item
int delayLoop = 0;
var guid = Guid.NewGuid();
logger.LogInformation("Queued work item {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 work item {Guid} is running. {DelayLoop}/3", guid, delayLoop);
}
if (delayLoop is 3)
{
logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}
else
{
logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
}
}
}
Substitua o conteúdo existente Program
pelo seguinte código C#:
using App.QueueService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<MonitorLoop>();
builder.Services.AddHostedService<QueuedHostedService>();
builder.Services.AddSingleton<IBackgroundTaskQueue>(_ =>
{
if (!int.TryParse(builder.Configuration["QueueCapacity"], out var queueCapacity))
{
queueCapacity = 100;
}
return new DefaultBackgroundTaskQueue(queueCapacity);
});
IHost host = builder.Build();
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
host.Run();
Os serviços estão registados em (Programa.cs). O serviço hospedado é registrado com o AddHostedService
método de extensão. MonitorLoop
é iniciado na instrução de nível superior .cs Programa:
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
Para obter mais informações sobre como registrar serviços, consulte Injeção de dependência no .NET.
Verificar a funcionalidade do serviço
Para executar o aplicativo do Visual Studio, selecione F5 ou selecione a opção de menu Depurar Iniciar Depuração>. Se você estiver usando a CLI do .NET, execute o dotnet run
comando no diretório de trabalho:
dotnet run
Para obter mais informações sobre o comando .NET CLI run, consulte dotnet run.
Quando solicitado, digite o w
(ou W
) pelo menos uma vez para enfileirar um item de trabalho emulado, conforme mostrado na saída de exemplo:
info: App.QueueService.MonitorLoop[0]
MonitorAsync loop is starting.
info: App.QueueService.QueuedHostedService[0]
QueuedHostedService is running.
Tap W to add a work item to the background queue.
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: .\queue-service
winfo: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is starting.
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 1/3
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 2/3
info: App.QueueService.MonitorLoop[0]
Queued work item 8453f845-ea4a-4bcb-b26e-c76c0d89303e is running. 3/3
info: App.QueueService.MonitorLoop[0]
Queued Background Task 8453f845-ea4a-4bcb-b26e-c76c0d89303e is complete.
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
info: App.QueueService.QueuedHostedService[0]
QueuedHostedService is stopping.
Se estiver executando o aplicativo de dentro do Visual Studio, selecione Depurar Parar Depuração>.... Como alternativa, selecione Ctrl + C na janela do console para sinalizar o cancelamento.