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.
Par Jeow Li Huan
Note
Ceci n’est pas la dernière version de cet article. Pour la version actuelle, consultez la version .NET 10 de cet article.
Warning
Cette version d’ASP.NET Core n’est plus prise en charge. Pour plus d’informations, consultez la stratégie de support .NET et .NET Core. Pour la version actuelle, consultez la version .NET 10 de cet article.
Dans ASP.NET Core, les tâches d’arrière-plan peuvent être implémentées en tant que services hébergés. Un service hébergé est une classe avec la logique de tâches en arrière-plan qui implémente l’interface IHostedService. Les services hébergés peuvent être utilisés dans les applications Worker Service et dans les applications web. Cet article contient trois exemples de service hébergé :
- Tâche d’arrière-plan qui s’exécute sur un minuteur.
- Service hébergé qui active un service délimité. Le service délimité peut utiliser l’injection de dépendances.
- Tâches d’arrière-plan en file d’attente qui s’exécutent séquentiellement.
Modèle de Service de Travailleur
Le modèle Service Worker ASP.NET Core fournit un point de départ pour l’écriture d’applications de service durables. Une application créée à partir du modèle de service Worker spécifie le SDK Worker dans son fichier projet :
<Project Sdk="Microsoft.NET.Sdk.Worker">
Pour utiliser le modèle en tant que base d’une application de services hébergés :
- Créer un nouveau projet.
- Sélectionnez Worker Service. Cliquez sur Suivant.
- Indiquez un nom de projet dans le champ Nom du projet, ou acceptez le nom de projet par défaut. Cliquez sur Suivant.
- Dans la boîte de dialogue Informations supplémentaires, choisissez un Framework. Cliquez sur Créer.
Package
Une application basée sur le modèle Worker Service utilise le Microsoft.NET.Sdk.Worker Kit de développement logiciel (SDK) et possède une référence de package explicite au Microsoft.Extensions.Hosting package NuGet. Par exemple, consultez le fichier projet de l’exemple d’application Worker Service (BackgroundTasksSample.csproj).
Pour les applications web qui utilisent le Microsoft.NET.Sdk.Web Kit de développement logiciel (SDK), le Microsoft.Extensions.Hosting package NuGet est référencé implicitement à partir du framework partagé. Une référence de package explicite dans le fichier projet de l’application n’est pas nécessaire.
Services hébergés dans une application web
Les services hébergés ne sont pas limités aux applications de service Worker. Une application web qui utilise le SDK Microsoft.NET.Sdk.Web inscrit des services hébergés dans le fichier Program en appelant la même AddHostedService méthode d’extension sur WebApplicationBuilder.Services. L’hôte démarre et arrête automatiquement les services hébergés inscrits, comme décrit dans la section sur l’interface IHostedService.
Le fichier suivant Program provient de l’exemple d’application web (BackgroundTasksWebSample). Il inscrit les trois services hébergés décrits dans le reste de cet article, mappe un point de terminaison racine qui retourne un message d’état et ajoute un point de terminaison qui met en file d’attente un élément de travail pour l’un d’eux :
using BackgroundTasksWebSample.Services;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<TimedHostedService>();
builder.Services.AddHostedService<ConsumeScopedServiceHostedService>();
builder.Services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
builder.Services.AddHostedService<QueuedHostedService>();
builder.Services.AddSingleton<IBackgroundTaskQueue>(_ =>
new BackgroundTaskQueue(builder.Configuration.GetValue("QueueCapacity", 100)));
var app = builder.Build();
app.MapGet("/", () => "Hosted service is running in the background.");
app.MapPost("/queue", async (
IBackgroundTaskQueue taskQueue,
ILogger<Program> logger,
CancellationToken cancellationToken) =>
{
await taskQueue.QueueBackgroundWorkItemAsync(async token =>
{
var guid = Guid.NewGuid();
logger.LogInformation("Queued Background Task {Guid} is starting.", guid);
try
{
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
catch (OperationCanceledException)
{
logger.LogInformation("Queued Background Task {Guid} was cancelled.", guid);
return;
}
logger.LogInformation("Queued Background Task {Guid} is complete.", guid);
}, cancellationToken);
return Results.Accepted();
});
app.Run();
Dans le code précédent :
-
TimedHostedService,ConsumeScopedServiceHostedService, etQueuedHostedServicesont enregistrés avec la méthode d’extension AddHostedService. - Un service hébergé est inscrit en tant que singleton, de sorte qu'il ne peut pas recevoir de services de portée, tels qu'un Entity Framework Core
DbContext, par injection de constructeur.IScopedProcessingServiceest inscrit en tant que service de portée etConsumeScopedServiceHostedServicecrée une portée pour le résoudre. Pour plus d’informations, consultez la section Consommation d’un service de portée dans une tâche en arrière-plan. - Le
IBackgroundTaskQueuesingleton est consommé par le/queuepoint de terminaison etQueuedHostedService. Une requêtePOSTvers/queuerenvoie une réponse202 Acceptedune fois que l’élément de travail est mis en file d’attente sans attendre qu’il s’exécute. Si la file d’attente limitée est pleine, la requête attend que l’espace soit disponible pour mettre en file d’attente l’élément de travail.QueuedHostedServiceexécute les éléments de travail mis en file d’attente en arrière-plan. Pour plus d’informations, consultez la section Tâches en arrière-plan mises en file d’attente .
Les extraits de code d’inscription de service suivants proviennent de l’exemple d’application Worker Service (BackgroundTasksSample), où services est le IServiceCollection paramètre passé à ConfigureServices. Dans une application web, utilisez builder.Services au lieu de services.
Interface IHostedService
L’interface IHostedService définit deux méthodes pour les objets qui sont gérés par l’hôte :
StartAsync
StartAsync(CancellationToken) contient la logique pour démarrer la tâche d’arrière-plan.
StartAsync est appelé avant :
- Le pipeline de traitement des requêtes de l'application est configuré.
- Le serveur soit démarré et IApplicationLifetime.ApplicationStarted soit déclenché.
StartAsync doit être limité aux tâches en cours d’exécution, car les services hébergés sont exécutés de manière séquentielle, et aucun autre service n’est démarré tant que les exécutions StartAsync ne sont pas terminées.
Les instances de service hébergées démarrent dans l’ordre dans lequel elles sont inscrites dans le conteneur d’injection de dépendances, sauf si l’application opte pour le démarrage simultané en définissant ServicesStartConcurrently sur true:
builder.Services.Configure<HostOptions>(options =>
{
options.ServicesStartConcurrently = true;
});
StopAsync
-
StopAsync(CancellationToken) se déclenche quand l’hôte effectue un arrêt en douceur.
StopAsynccontient la logique pour terminer la tâche d’arrière-plan. Implémentez IDisposable et les finaliseurs (destructeurs) pour supprimer toutes les ressources non managées.
Le jeton d’annulation a un délai d’expiration par défaut de 30 secondes pour indiquer que le processus d’arrêt ne doit plus être effectué en douceur. Quand l’annulation est demandée sur le jeton :
- Les opérations en arrière-plan restantes effectuées par l’application doivent être abandonnées.
- Les méthodes appelées dans
StopAsyncdoivent retourner rapidement.
Cependant, les tâches ne sont pas abandonnées après la demande d’annulation : l’appelant attend que toutes les tâches se terminent.
Si l’application s’arrête inopinément (par exemple en cas d’échec du processus de l’application), StopAsync n’est probablement pas appelée. Par conséquent, les méthodes appelées ou les opérations effectuées dans StopAsync peuvent ne pas se produire.
Pour prolonger le délai d’expiration de 30 secondes du processus d'arrêt par défaut, définissez :
- ShutdownTimeout quand vous utilisez l’hôte générique. Pour plus d’informations, consultez Hôte générique .NET dans ASP.NET Core.
- Le paramètre de configuration du délai d’expiration de l’hôte quand vous utilisez l’hôte web. Pour plus d’informations, consultez Hôte web ASP.NET Core.
Le service hébergé est activé une seule fois au démarrage de l’application et s’arrête normalement à l’arrêt de l’application. Si une erreur est levée pendant l’exécution des tâches d’arrière-plan, Dispose doit être appelée même si StopAsync n’est pas appelée.
Les instances de service hébergées s’arrêtent dans l’ordre inverse qu’elles sont inscrites dans le conteneur d’injection de dépendances, sauf si l’application opte pour le comportement d’arrêt simultané en définissant ServicesStopConcurrently sur true:
builder.Services.Configure<HostOptions>(options =>
{
options.ServicesStopConcurrently = true;
});
Classe de base BackgroundService
BackgroundService est une classe de base pour l’implémentation d’un IHostedService à exécution longue.
ExecuteAsync(CancellationToken) est appelé sur le pool de threads pour exécuter le service en arrière-plan. L’implémentation retourne un Task qui représente la durée de vie entière du service en arrière-plan. L'hôte est bloqué dans StopAsync(CancellationToken) en attendant que ExecuteAsync se termine.
ExecuteAsync(CancellationToken) est appelé pour exécuter le service en arrière-plan. L’implémentation retourne un Task qui représente la durée de vie entière du service en arrière-plan. Aucun autre service n’est démarré tant qu’ExecuteAsync n’est pas rendu asynchrone, par exemple en appelant await. Évitez d’effectuer un travail d’initialisation long et bloquant dans ExecuteAsync. L'hôte est bloqué dans StopAsync(CancellationToken) en attendant que ExecuteAsync se termine.
Le jeton d’annulation est déclenché lorsque IHostedService.StopAsync est appelé. Votre implémentation de ExecuteAsync doit se terminer rapidement lorsque le jeton d’annulation est déclenché afin d’arrêter normalement le service. Sinon, le service s’arrête brutalement à l’expiration du délai d’arrêt. Pour plus d’informations, consultez la section Interface IHostedService.
Pour plus d’informations, consultez le code source de BackgroundService.
Tâches d'arrière-plan temporisées
Une tâche d’arrière-plan avec minuteur utilise la classe System.Threading.Timer. Le minuteur déclenche la méthode DoWork de la tâche. Le minuteur est désactivé sur StopAsync et supprimé quand le conteneur du service est supprimé sur Dispose :
public class TimedHostedService(ILogger<TimedHostedService> logger) : IHostedService, IDisposable
{
private int executionCount = 0;
private Timer? _timer = null;
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();
}
}
Le Timer n’attend pas la fin des exécutions précédentes de DoWork pour finir, de sorte que l’approche indiquée peut ne pas convenir à tous les scénarios.
Interlocked.Increment est utilisé pour incrémenter le compteur d’exécution en tant qu’opération atomique, ce qui garantit que plusieurs threads ne mettent pas à jour executionCount simultanément.
Le service est inscrit dans le Program fichier avec la méthode d’extension AddHostedService :
services.AddHostedService<TimedHostedService>();
Utilisation d’un service à portée limitée dans une tâche d’arrière-plan
Pour utiliser des services délimités au sein d’un BackgroundService, créez une étendue. Par défaut, aucune étendue n’est créée pour un service hébergé.
Le service des tâches d’arrière-plan délimitées contient la logique de la tâche d’arrière-plan. Dans l’exemple suivant :
- Le service est asynchrone. La méthode
DoWorkretourne unTask. À des fins de démonstration, un délai de dix secondes est attendu dans laDoWorkméthode. - Une ILogger est injectée dans le service.
internal interface IScopedProcessingService
{
Task DoWork(CancellationToken stoppingToken);
}
internal class ScopedProcessingService(
ILogger<ScopedProcessingService> logger) : IScopedProcessingService
{
private int executionCount = 0;
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);
}
}
}
Le service hébergé crée une étendue pour résoudre le service des tâches d’arrière-plan délimitées pour appeler sa méthode DoWork.
DoWork retourne un Task, qui est attendu dans ExecuteAsync :
public class ConsumeScopedServiceHostedService(
IServiceProvider services,
ILogger<ConsumeScopedServiceHostedService> logger) : BackgroundService
{
public IServiceProvider Services { get; } = services;
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);
}
}
Les services sont inscrits dans le Program fichier. Le service hébergé est inscrit avec la méthode d’extension AddHostedService :
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tâches d’arrière-plan en file d’attente
Une file d’attente de tâches en arrière-plan est basée sur .NET Framework 4.x QueueBackgroundWorkItem:
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(Func<CancellationToken, ValueTask> workItem);
ValueTask<Func<CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
public class BackgroundTaskQueue(int capacity) : IBackgroundTaskQueue
{
// Capacity should be set based on the expected application load and
// number of concurrent threads accessing the queue.
// BoundedChannelFullMode.Wait causes calls to WriteAsync() to return a task,
// which only completes when space becomes available. In the event that many
// publishers/calls start accumulating, using BoundedChannelFullMode.Wait can
// lead to backpressure.
private readonly Channel<Func<CancellationToken, ValueTask>> _queue =
Channel.CreateBounded<Func<CancellationToken, ValueTask>>(new BoundedChannelOptions(capacity)
{
FullMode = BoundedChannelFullMode.Wait
});
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;
}
}
Dans l’exemple QueueHostedService suivant :
- La méthode
BackgroundProcessingretourne uneTask, qui est attendue dansExecuteAsync. - Les tâches d’arrière-plan de la file d’attente sont sorties de la file et exécutées dans
BackgroundProcessing. - Les éléments de travail sont attendus avant que le service s’arrête dans
StopAsync.
public class QueuedHostedService(
IBackgroundTaskQueue taskQueue,
ILogger<QueuedHostedService> logger) : BackgroundService
{
public IBackgroundTaskQueue TaskQueue { get; } = taskQueue;
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);
}
}
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.QueueBackgroundWorkItemest 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 mis en œuvre (
Task.Delay). - Une instruction
try-catchcapture OperationCanceledException si la tâche est annulée.
- Trois délais de cinq secondes sont mis en œuvre (
public class MonitorLoop(
IBackgroundTaskQueue taskQueue,
ILogger<MonitorLoop> logger,
IHostApplicationLifetime applicationLifetime)
{
private readonly CancellationToken _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);
}
}
}
Les services sont inscrits dans le fichier Program. Le service hébergé est inscrit avec la méthode d’extension AddHostedService :
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 est démarré dans le Program fichier :
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Dans une application web, un endpoint peut mettre en file d’attente des éléments de travail au lieu d’une boucle de lecture sur la console. Pour obtenir un exemple, consultez la section Services hébergés dans une application web.
Tâche en arrière-plan programmée asynchrone
Le code suivant crée une tâche en arrière-plan minutée asynchrone :
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 natif
Les modèles Worker Service prennent en charge la compilation anticipée native (AOT) .NET avec l’indicateur --aot :
- Créer un nouveau projet.
- Sélectionnez Worker Service. Cliquez sur Suivant.
- Indiquez un nom de projet dans le champ Nom du projet, ou acceptez le nom de projet par défaut. Cliquez sur Suivant.
- Dans la boîte de dialogue Informations supplémentaires :
- Choisissez un Framework.
- Cochez la case Activer la publication Native AOT.
- Cliquez sur Créer.
L’option AOT ajoute <PublishAot>true</PublishAot> au fichier projet :
<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>
Ressources supplémentaires
- Tests unitaires des services en arrière-plan sur GitHub.
- Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
- Implémenter des tâches d’arrière-plan dans des microservices avec IHostedService et la classe BackgroundService
- Exécuter des tâches en arrière-plan avec WebJobs dans Azure App Service
- Timer
Dans ASP.NET Core, les tâches d’arrière-plan peuvent être implémentées en tant que services hébergés. Un service hébergé est une classe avec la logique de tâches en arrière-plan qui implémente l’interface IHostedService. Cet article contient trois exemples de service hébergé :
- Tâche d’arrière-plan qui s’exécute sur un minuteur.
- Service hébergé qui active un service délimité. Le service délimité peut utiliser l’injection de dépendances.
- Tâches d’arrière-plan en file d’attente qui s’exécutent séquentiellement.
Modèle de Service de Travailleur
Le modèle Service Worker ASP.NET Core fournit un point de départ pour l’écriture d’applications de service durables. Une application créée à partir du modèle de service Worker spécifie le SDK Worker dans son fichier projet :
<Project Sdk="Microsoft.NET.Sdk.Worker">
Pour utiliser le modèle en tant que base d’une application de services hébergés :
- Créer un nouveau projet.
- Sélectionnez Worker Service. Cliquez sur Suivant.
- Indiquez un nom de projet dans le champ Nom du projet, ou acceptez le nom de projet par défaut. Cliquez sur Suivant.
- Dans la boîte de dialogue Informations supplémentaires, choisissez un Framework. Cliquez sur Créer.
Package
Une application basée sur le modèle Service Worker utilise le Kit de développement logiciel (SDK) Microsoft.NET.Sdk.Worker et possède une référence de package explicite au package Microsoft.Extensions.Hosting. Par exemple, consultez le fichier projet de l’exemple d’application (BackgroundTasksSample.csproj).
Pour les applications web qui utilisent le SDK Microsoft.NET.Sdk.Web, le package Microsoft.Extensions.Hosting est référencé implicitement à partir du framework partagé. Une référence de package explicite dans le fichier projet de l’application n’est pas nécessaire.
Interface IHostedService
L’interface IHostedService définit deux méthodes pour les objets qui sont gérés par l’hôte :
StartAsync
StartAsync(CancellationToken) contient la logique pour démarrer la tâche d’arrière-plan.
StartAsync est appelé avant :
- Le pipeline de traitement des requêtes de l'application est configuré.
- Le serveur soit démarré et IApplicationLifetime.ApplicationStarted soit déclenché.
StartAsync doit être limité aux tâches en cours d’exécution, car les services hébergés sont exécutés de manière séquentielle, et aucun autre service n’est démarré tant que les exécutions StartAsync ne sont pas terminées.
StopAsync
-
StopAsync(CancellationToken) se déclenche quand l’hôte effectue un arrêt en douceur.
StopAsynccontient la logique pour terminer la tâche d’arrière-plan. Implémentez IDisposable et les finaliseurs (destructeurs) pour supprimer toutes les ressources non managées.
Le jeton d’annulation a un délai d’expiration par défaut de 30 secondes pour indiquer que le processus d’arrêt ne doit plus être effectué en douceur. Quand l’annulation est demandée sur le jeton :
- Les opérations en arrière-plan restantes effectuées par l’application doivent être abandonnées.
- Les méthodes appelées dans
StopAsyncdoivent retourner rapidement.
Cependant, les tâches ne sont pas abandonnées après la demande d’annulation : l’appelant attend que toutes les tâches se terminent.
Si l’application s’arrête inopinément (par exemple en cas d’échec du processus de l’application), StopAsync n’est probablement pas appelée. Par conséquent, les méthodes appelées ou les opérations effectuées dans StopAsync peuvent ne pas se produire.
Pour prolonger le délai d’expiration de 30 secondes du processus d'arrêt par défaut, définissez :
- ShutdownTimeout quand vous utilisez l’hôte générique. Pour plus d’informations, consultez Hôte générique .NET dans ASP.NET Core.
- Le paramètre de configuration du délai d’expiration de l’hôte quand vous utilisez l’hôte web. Pour plus d’informations, consultez Hôte web ASP.NET Core.
Le service hébergé est activé une seule fois au démarrage de l’application et s’arrête normalement à l’arrêt de l’application. Si une erreur est levée pendant l’exécution des tâches d’arrière-plan, Dispose doit être appelée même si StopAsync n’est pas appelée.
Classe de base BackgroundService
BackgroundService est une classe de base pour l’implémentation d’un IHostedService à exécution longue.
ExecuteAsync(CancellationToken) est appelé pour exécuter le service en arrière-plan. L’implémentation retourne un Task qui représente la durée de vie entière du service en arrière-plan. Aucun autre service n’est démarré tant qu’ExecuteAsync n’est pas rendu asynchrone, par exemple en appelant await. Évitez d’effectuer un travail d’initialisation long et bloquant dans ExecuteAsync. L'hôte est bloqué dans StopAsync(CancellationToken) en attendant que ExecuteAsync se termine.
Le jeton d’annulation est déclenché lorsque IHostedService.StopAsync est appelé. Votre implémentation de ExecuteAsync doit se terminer rapidement lorsque le jeton d’annulation est déclenché afin d’arrêter normalement le service. Sinon, le service s’arrête brutalement à l’expiration du délai d’arrêt. Pour plus d’informations, consultez la section Interface IHostedService.
Pour plus d’informations, consultez le code source de BackgroundService.
Tâches d'arrière-plan temporisées
Une tâche d’arrière-plan avec minuteur utilise la classe System.Threading.Timer. Le minuteur déclenche la méthode DoWork de la tâche. Le minuteur est désactivé sur StopAsync et supprimé quand le conteneur du service est supprimé sur 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();
}
}
Le Timer n’attend pas la fin des exécutions précédentes de DoWork pour finir, de sorte que l’approche indiquée peut ne pas convenir à tous les scénarios.
Interlocked.Increment est utilisé pour incrémenter le compteur d’exécution en tant qu’opération atomique, ce qui garantit que plusieurs threads ne mettent pas à jour executionCount simultanément.
Le service est inscrit dans IHostBuilder.ConfigureServices (Program.cs) avec la méthode d’extension AddHostedService :
services.AddHostedService<TimedHostedService>();
Utilisation d’un service à portée limitée dans une tâche d’arrière-plan
Pour utiliser des services délimités au sein d’un BackgroundService, créez une étendue. Par défaut, aucune étendue n’est créée pour un service hébergé.
Le service des tâches d’arrière-plan délimitées contient la logique de la tâche d’arrière-plan. Dans l’exemple suivant :
- Le service est asynchrone. La méthode
DoWorkretourne unTask. À des fins de démonstration, un délai de dix secondes est attendu dans laDoWorkméthode. - Une ILogger est injectée dans le service.
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);
}
}
}
Le service hébergé crée une étendue pour résoudre le service des tâches d’arrière-plan délimitées pour appeler sa méthode DoWork.
DoWork retourne un Task, qui est attendu dans 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);
}
}
Les services sont enregistrés à IHostBuilder.ConfigureServices (Program.cs). Le service hébergé est inscrit avec la méthode d’extension AddHostedService :
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tâches d’arrière-plan en file d’attente
Une file d’attente de tâches en arrière-plan est basée sur .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 causes calls to WriteAsync() to return a task,
// which only completes when space becomes available. In the event that many
// publishers/calls start accumulating, using BoundedChannelFullMode.Wait can
// lead to backpressure.
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;
}
}
Dans l’exemple QueueHostedService suivant :
- La méthode
BackgroundProcessingretourne uneTask, qui est attendue dansExecuteAsync. - Les tâches d’arrière-plan de la file d’attente sont sorties de la file et exécutées dans
BackgroundProcessing. - Les éléments de travail sont attendus avant que le service s’arrête dans
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);
}
}
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.QueueBackgroundWorkItemest 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 mis en œuvre (
Task.Delay). - Une instruction
try-catchcapture OperationCanceledException si la tâche est annulée.
- Trois délais de cinq secondes sont mis en œuvre (
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);
}
}
}
Les services sont enregistrés à IHostBuilder.ConfigureServices (Program.cs). Le service hébergé est inscrit avec la méthode d’extension AddHostedService :
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 est démarré dans Program.cs :
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();
Tâche en arrière-plan programmée asynchrone
Le code suivant crée une tâche en arrière-plan minutée asynchrone :
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);
}
}
Ressources supplémentaires
- Tests unitaires des services en arrière-plan sur GitHub.
- Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
- Implémenter des tâches d’arrière-plan dans des microservices avec IHostedService et la classe BackgroundService
- Exécuter des tâches en arrière-plan avec WebJobs dans Azure App Service
- Timer
Dans ASP.NET Core, les tâches d’arrière-plan peuvent être implémentées en tant que services hébergés. Un service hébergé est une classe avec la logique de tâches en arrière-plan qui implémente l’interface IHostedService. Cet article contient trois exemples de service hébergé :
- Tâche d’arrière-plan qui s’exécute sur un minuteur.
- Service hébergé qui active un service délimité. Le service délimité peut utiliser l’injection de dépendances.
- Tâches d’arrière-plan en file d’attente qui s’exécutent séquentiellement.
Affichez ou téléchargez l’exemple de code (procédure de téléchargement)
Modèle de Service de Travailleur
Le modèle Service Worker ASP.NET Core fournit un point de départ pour l’écriture d’applications de service durables. Une application créée à partir du modèle de service Worker spécifie le SDK Worker dans son fichier projet :
<Project Sdk="Microsoft.NET.Sdk.Worker">
Pour utiliser le modèle en tant que base d’une application de services hébergés :
- Créer un nouveau projet.
- Sélectionnez Worker Service. Cliquez sur Suivant.
- Indiquez un nom de projet dans le champ Nom du projet, ou acceptez le nom de projet par défaut. Cliquez sur Créer.
- Dans la boîte de dialogue Créer un service Worker, sélectionnez Créer.
Package
Une application basée sur le modèle Service Worker utilise le Kit de développement logiciel (SDK) Microsoft.NET.Sdk.Worker et possède une référence de package explicite au package Microsoft.Extensions.Hosting. Par exemple, consultez le fichier projet de l’exemple d’application (BackgroundTasksSample.csproj).
Pour les applications web qui utilisent le SDK Microsoft.NET.Sdk.Web, le package Microsoft.Extensions.Hosting est référencé implicitement à partir du framework partagé. Une référence de package explicite dans le fichier projet de l’application n’est pas nécessaire.
Interface IHostedService
L’interface IHostedService définit deux méthodes pour les objets qui sont gérés par l’hôte :
StartAsync
StartAsync contient la logique pour démarrer la tâche d’arrière-plan.
StartAsync est appelé avant :
- Le pipeline de traitement des requêtes de l'application est configuré.
- Le serveur soit démarré et IApplicationLifetime.ApplicationStarted soit déclenché.
Le comportement par défaut peut être modifié afin que le StartAsync du service hébergé s’exécute une fois le pipeline de l’application configuré et ApplicationStarted appelé. Pour modifier le comportement par défaut, ajoutez le service hébergé (VideosWatcher dans l’exemple suivant) après avoir appelé 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) se déclenche quand l’hôte effectue un arrêt en douceur.
StopAsynccontient la logique pour terminer la tâche d’arrière-plan. Implémentez IDisposable et les finaliseurs (destructeurs) pour supprimer toutes les ressources non managées.
Le jeton d'annulation a un délai d'expiration par défaut de cinq secondes pour indiquer que le processus d'arrêt ne doit plus être gracieux. Quand l’annulation est demandée sur le jeton :
- Les opérations en arrière-plan restantes effectuées par l’application doivent être abandonnées.
- Les méthodes appelées dans
StopAsyncdoivent retourner rapidement.
Cependant, les tâches ne sont pas abandonnées après la demande d’annulation : l’appelant attend que toutes les tâches se terminent.
Si l’application s’arrête inopinément (par exemple en cas d’échec du processus de l’application), StopAsync n’est probablement pas appelée. Par conséquent, les méthodes appelées ou les opérations effectuées dans StopAsync peuvent ne pas se produire.
Pour prolonger le délai d'arrêt par défaut de cinq secondes, définissez :
- ShutdownTimeout quand vous utilisez l’hôte générique. Pour plus d’informations, consultez Hôte générique .NET dans ASP.NET Core.
- Le paramètre de configuration du délai d’expiration de l’hôte quand vous utilisez l’hôte web. Pour plus d’informations, consultez Hôte web ASP.NET Core.
Le service hébergé est activé une seule fois au démarrage de l’application et s’arrête normalement à l’arrêt de l’application. Si une erreur est levée pendant l’exécution des tâches d’arrière-plan, Dispose doit être appelée même si StopAsync n’est pas appelée.
Classe de base BackgroundService
BackgroundService est une classe de base pour l’implémentation d’un IHostedService à exécution longue.
ExecuteAsync(CancellationToken) est appelé pour exécuter le service en arrière-plan. L’implémentation retourne un Task qui représente la durée de vie entière du service en arrière-plan. Aucun autre service n’est démarré tant qu’ExecuteAsync n’est pas rendu asynchrone, par exemple en appelant await. Évitez d’effectuer un travail d’initialisation long et bloquant dans ExecuteAsync. L'hôte est bloqué dans StopAsync(CancellationToken) en attendant que ExecuteAsync se termine.
Le jeton d’annulation est déclenché lorsque IHostedService.StopAsync est appelé. Votre implémentation de ExecuteAsync doit se terminer rapidement lorsque le jeton d’annulation est déclenché afin d’arrêter normalement le service. Sinon, le service s’arrête brutalement à l’expiration du délai d’arrêt. Pour plus d’informations, consultez la section Interface IHostedService.
StartAsync doit être limité aux tâches en cours d’exécution, car les services hébergés sont exécutés de manière séquentielle, et aucun autre service n’est démarré tant que les exécutions StartAsync ne sont pas terminées. Les tâches de longue durée doivent être placées dans ExecuteAsync. Pour plus d’informations, consultez la source de BackgroundService.
Tâches d'arrière-plan temporisées
Une tâche d’arrière-plan avec minuteur utilise la classe System.Threading.Timer. Le minuteur déclenche la méthode DoWork de la tâche. Le minuteur est désactivé sur StopAsync et supprimé quand le conteneur du service est supprimé sur 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();
}
}
Le Timer n’attend pas la fin des exécutions précédentes de DoWork pour finir, de sorte que l’approche indiquée peut ne pas convenir à tous les scénarios.
Interlocked.Increment est utilisé pour incrémenter le compteur d’exécution en tant qu’opération atomique, ce qui garantit que plusieurs threads ne mettent pas à jour executionCount simultanément.
Le service est inscrit dans IHostBuilder.ConfigureServices (Program.cs) avec la méthode d’extension AddHostedService :
services.AddHostedService<TimedHostedService>();
Utilisation d’un service à portée limitée dans une tâche d’arrière-plan
Pour utiliser des services délimités au sein d’un BackgroundService, créez une étendue. Par défaut, aucune étendue n’est créée pour un service hébergé.
Le service des tâches d’arrière-plan délimitées contient la logique de la tâche d’arrière-plan. Dans l’exemple suivant :
- Le service est asynchrone. La méthode
DoWorkretourne unTask. À des fins de démonstration, un délai de dix secondes est attendu dans laDoWorkméthode. - Une ILogger est injectée dans le service.
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);
}
}
}
Le service hébergé crée une étendue pour résoudre le service des tâches d’arrière-plan délimitées pour appeler sa méthode DoWork.
DoWork retourne un Task, qui est attendu dans 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);
}
}
Les services sont enregistrés à IHostBuilder.ConfigureServices (Program.cs). Le service hébergé est inscrit avec la méthode d’extension AddHostedService :
services.AddHostedService<ConsumeScopedServiceHostedService>();
services.AddScoped<IScopedProcessingService, ScopedProcessingService>();
Tâches d’arrière-plan en file d’attente
Une file d’attente de tâches en arrière-plan est basée sur .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 causes calls to WriteAsync() to return a task,
// which only completes when space becomes available. In the event that many
// publishers/calls start accumulating, using BoundedChannelFullMode.Wait can
// lead to backpressure.
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;
}
}
Dans l’exemple QueueHostedService suivant :
- La méthode
BackgroundProcessingretourne uneTask, qui est attendue dansExecuteAsync. - Les tâches d’arrière-plan de la file d’attente sont sorties de la file et exécutées dans
BackgroundProcessing. - Les éléments de travail sont attendus avant que le service s’arrête dans
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);
}
}
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.QueueBackgroundWorkItemest 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 mis en œuvre (
Task.Delay). - Une instruction
try-catchcapture OperationCanceledException si la tâche est annulée.
- Trois délais de cinq secondes sont mis en œuvre (
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);
}
}
}
Les services sont enregistrés à IHostBuilder.ConfigureServices (Program.cs). Le service hébergé est inscrit avec la méthode d’extension AddHostedService :
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 est démarré dans Program.Main :
var monitorLoop = host.Services.GetRequiredService<MonitorLoop>();
monitorLoop.StartMonitorLoop();