Remarque
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
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 s’appuyant sur le modèle de service Worker, vous générez de nouvelles fonctionnalités en plus du BackgroundService.
Dans ce tutoriel, vous allez apprendre à :
- Créez un service de file d’attente.
- Déléguer le travail à une file d’attente de tâches.
- Enregistrer un écouteur d'événements clavier de console à partir des événements IHostApplicationLifetime.
Conseil / Astuce
Tous les exemples de code source « Workers in .NET » sont disponibles dans l’Explorateur d’exemples pour le téléchargement. Pour plus d’informations, consultez les exemples de code pour Workers dans .NET.
Prerequisites
- Kit de développement logiciel (SDK) .NET 8.0 ou version ultérieure
- Un environnement de développement intégré .NET (IDE)
- N’hésitez pas à utiliser Visual Studio
Créer 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 « Service Worker », puis sélectionnez modèle De service Worker. 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 <Project.Name> par le nom souhaité de votre projet.
dotnet new worker --name <Project.Name>
Pour plus d’informations sur la commande de création d'un nouveau projet de service worker de .NET CLI, consultez dotnet new worker.
Conseil / Astuce
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 / Astuce
Les fonctionnalités de l'espace de noms System.Web n'ont pas été portées intentionnellement vers .NET et restent exclusives à .NET Framework. Pour plus d’informations, consultez Commencer la migration incrémentielle de ASP.NET vers ASP.NET Core.
Dans .NET, pour modéliser un service inspiré par la QueueBackgroundWorkItem fonctionnalité, commencez par ajouter une IBackgroundTaskQueue interface 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, dont l'une expose des fonctionnalités de mise en file d’attente, et l'autre qui retire de la file d'attente les éléments de travail précédemment mis en file d'attente. Un élément de travail est un Func<CancellationToken, ValueTask>. Ensuite, ajoutez 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 une Channel<T> file d’attente. L’objet BoundedChannelOptions(Int32) est appelé avec une capacité explicite. La capacité doit être définie en fonction du chargement attendu de l’application et du nombre de threads simultanés accédant à la file d’attente. BoundedChannelFullMode.Wait entraîne les appels à ChannelWriter<T>.WriteAsync à retourner une tâche, qui se termine uniquement lorsque de l’espace devient disponible. Dans le cas où trop d'éditeurs/appels s'accumulent, cela entraîne une rétropression.
Réécrire la classe Worker
Dans l’exemple QueueHostedService suivant :
- La
ProcessTaskQueueAsyncméthode retourne un Task dansExecuteAsync. - 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 existante Worker par le code C# suivant, puis renommez le fichier en 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
IBackgroundTaskQueueest injectée dans le serviceMonitorLoop. -
IBackgroundTaskQueue.QueueBackgroundWorkItemAsyncest 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 retards de 5 secondes sont exécutés Delay.
- Une
try-catchinstruction intercepte 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 existant Program 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 initialisé dans la déclaration de niveau supérieur de Program.cs :
MonitorLoop monitorLoop = host.Services.GetRequiredService<MonitorLoop>()!;
monitorLoop.StartMonitorLoop();
Pour plus d’informations sur l’enregistrement des services, consultez Injection de Dépendances dans .NET.
Vérifier la fonctionnalité de 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 dotnet run commande à 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 w (ou W) au moins une fois pour mettre en file d'attente un élément de tâche émulée, comme montré 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.