Créer un service de file d’attente
Un service de file d’attente est un excellent exemple de service de longue durée, où les éléments de travail peuvent être mis en file d’attente et travaillés de manière séquentielle à mesure que les éléments de travail précédents sont terminés. En vous appuyant sur le modèle Worker Service, vous générez de nouvelles fonctionnalités par-dessus le BackgroundService.
Dans ce tutoriel, vous allez apprendre à :
- Créer un service de file d’attente.
- Déléguer le travail à une file d’attente de tâches.
- Inscrire un écouteur de clé de console à partir d’événements IHostApplicationLifetime .
Conseil
Tout le code source d’exemple « Workers dans .NET » peut être téléchargé dans l’Explorateur d’exemples. Pour plus d’informations, consultez Parcourir les exemples de code : Workers dans .NET.
Prérequis
- Kit de développement logiciel (SDK) .NET 8.0 ou versions antérieures
- Un IDE (environnement de développement intégré) .NET
- N’hésitez pas à utiliser Visual Studio
Création d'un projet
Pour créer un projet de service Worker avec Visual Studio, vous devez sélectionner Fichier>Nouveau>Projet.... Dans la boîte de dialogue Créer un projet, recherchez « Worker Service », puis sélectionnez Modèle Worker Service. Si vous préférez utiliser l’interface CLI .NET, ouvrez votre terminal favori dans un répertoire de travail. Exécutez la commande dotnet new
et remplacez le <Project.Name>
par le nom de projet souhaité.
dotnet new worker --name <Project.Name>
Pour plus d’informations sur la commande de projet de service new worker de l’interface CLI .NET, consultez dotnet new worker.
Conseil
Si vous utilisez Visual Studio Code, vous pouvez exécuter des commandes CLI .NET à partir du terminal intégré. Pour plus d’informations, consultez Visual Studio Code : Terminal intégré.
Créer des services de mise en file d’attente
Vous connaissez peut-être la fonctionnalité QueueBackgroundWorkItem(Func<CancellationToken,Task>) de l’espace de noms System.Web.Hosting
.
Conseil
Les fonctionnalités de l’espace de noms System.Web
n’ont pas été transférées intentionnellement vers .NET et restent exclusives au .NET Framework. Pour plus d’informations, consultez Prise en main de la migration incrémentielle d’ASP.NET vers ASP.NET Core.
Dans .NET, pour modéliser un service qui s’inspire de la fonctionnalité QueueBackgroundWorkItem
, commencez par ajouter une interface IBackgroundTaskQueue
au projet :
namespace App.QueueService;
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(
Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
Il existe deux méthodes : l’une qui expose la fonctionnalité de mise en file d’attente et l’autre qui supprime les éléments de travail précédemment mis en file d’attente. Un élément de travail est une Func<CancellationToken, ValueTask>
. Ajoutez ensuite l’implémentation par défaut au projet.
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;
}
}
L’implémentation précédente s’appuie sur un Channel<T> en tant que file d’attente. Le BoundedChannelOptions(Int32) est appelé avec une capacité explicite. La capacité doit être définie en fonction de la charge d’application attendue et du nombre de threads simultanés accédant à la file d’attente. BoundedChannelFullMode.Wait entraîne les appels à ChannelWriter<T>.WriteAsync pour renvoyer une tâche, qui ne se termine que lorsque l’espace est disponible. Ce qui entraîne une contre-pression, au cas où trop d’éditeurs/appels commenceraient à s’accumuler.
Réécrire la classe Worker
Dans l’exemple QueueHostedService
suivant :
- La méthode
ProcessTaskQueueAsync
retourne une Task enExecuteAsync
. - Les tâches d’arrière-plan de la file d’attente sont sorties de la file et exécutées dans
ProcessTaskQueueAsync
. - Les éléments de travail sont attendus avant que le service s’arrête dans
StopAsync
.
Remplacez la classe Worker
existante par le code C# suivant et renommez le fichier 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);
}
}
Un service MonitorLoop
gère les tâches de mise en file d’attente pour le service hébergé chaque fois que la clé w
est sélectionnée sur un périphérique d’entrée :
- La
IBackgroundTaskQueue
est injectée dans le serviceMonitorLoop
. IBackgroundTaskQueue.QueueBackgroundWorkItemAsync
est appelée pour mettre un élément de travail en file d’attente.- L’élément de travail simule une tâche en arrière-plan de longue durée :
- Trois délais de cinq secondes sont exécutés Delay.
- Une instruction
try-catch
capture OperationCanceledException si la tâche est annulée.
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);
}
}
}
Remplacez le contenu Program
existant par le code C# suivant :
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();
Les services sont inscrits dans (Program.cs). Le service hébergé est inscrit avec la méthode d’extension AddHostedService
. MonitorLoop
est démarré dans l’instruction de niveau supérieur Program.cs :
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
Pour plus d’informations sur l’inscription de services, consultez Injection de dépendances dans .NET.
Vérifier les fonctionnalités du service
Pour exécuter l’application à partir de Visual Studio, sélectionnez F5 ou sélectionnez l’option de menu Déboguer>Démarrer le débogage. Si vous utilisez l’interface CLI .NET, exécutez la commande dotnet run
à partir du répertoire de travail :
dotnet run
Pour plus d’informations sur la commande d’exécution de l’interface CLI .NET, consultez dotnet run.
Lorsque vous y êtes invité, entrez le w
(ou W
) au moins une fois pour mettre en file d’attente un élément de travail émulé, comme indiqué dans l’exemple de sortie :
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.
Si vous exécutez l’application à partir de Visual Studio, sélectionnez Déboguer>Arrêter le débogage.... Vous pouvez également sélectionner Ctrl + C dans la fenêtre de console pour signaler l’annulation.