Partager via


Services de travail dans .NET

Il existe de nombreuses raisons de créer des services de longue durée, tels que :

  • Traitement des données nécessitant beaucoup de CPU.
  • Mise en file d’attente des éléments de travail en arrière-plan.
  • Exécution d’une opération basée sur le temps selon une planification.

Le traitement du service en arrière-plan n’implique généralement pas d’interface utilisateur, mais les interfaces utilisateur peuvent être créées autour de celles-ci. Dans les premiers jours avec .NET Framework, les développeurs Windows pourraient créer des services Windows à ces fins. Maintenant, avec .NET, vous pouvez utiliser le BackgroundService, qui est une implémentation de IHostedService, ou implémenter votre propre.

Avec .NET, vous n’êtes plus limité à Windows. Vous pouvez développer des services en arrière-plan multiplateforme. Les services hébergés sont prêts pour la journalisation des logs, pour la configuration, et pour l'injection de dépendances. Ils font partie de la suite d’extensions de bibliothèques, ce qui signifie qu’ils sont fondamentaux pour toutes les charges de travail .NET qui fonctionnent avec l’hôte générique.

Important

L’installation du Kit de développement logiciel (SDK) .NET installe également Microsoft.NET.Sdk.Worker et le modèle Worker. En d’autres termes, après avoir installé le Kit de développement logiciel (SDK) .NET, vous pouvez créer un worker à l’aide de la commande dotnet new worker . Si vous utilisez Visual Studio, le modèle est masqué jusqu’à ce que la charge de travail de développement web et ASP.NET facultative soit installée.

Terminologie

De nombreux termes sont utilisés par erreur synonymes. Cette section définit certains de ces termes pour rendre leur intention plus évidente dans cet article.

  • Service en arrière-plan : type BackgroundService .
  • Service hébergé : implémentations du IHostedService, ou de la IHostedService elle-même.
  • Service de longue durée : Tout service qui s’exécute en continu.
  • Service Windows : infrastructure de service Windows , centrée à l’origine sur .NET Framework, mais désormais accessible via .NET.
  • Service Worker : modèle Service Worker.

Modèle Service Worker

Le modèle de service Worker est disponible dans l’interface CLI .NET et Visual Studio. Pour plus d’informations, consultez l’interface CLI .NET, dotnet new worker - modèle. Le modèle se compose d'une classe Program et d'une classe Worker.

using App.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

IHost host = builder.Build();
host.Run();

La classe précédente Program :

  • Crée un HostApplicationBuilder.
  • Appel AddHostedService pour inscrire Worker en tant que service hébergé.
  • Génère une IHost version depuis le générateur.
  • Appelle Run sur l’instance host, qui exécute l’application.

Valeurs par défaut du modèle

Le modèle Worker n’active pas par défaut le ramasse-miettes de serveurs, car il existe de nombreux facteurs qui jouent un rôle dans la détermination de sa nécessité. Tous les scénarios nécessitant des services de longue durée doivent prendre en compte les implications en termes de performances de cette valeur par défaut. Pour activer le GC du serveur, ajoutez le nœud "ServerGarbageCollection" au fichier de projet :

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

Compromis et considérations

Activé Handicapé
Gestion efficace de la mémoire : récupère automatiquement la mémoire inutilisée pour éviter les fuites de mémoire et optimiser l’utilisation des ressources. Amélioration des performances en temps réel : évite les pauses ou interruptions potentielles causées par la collecte des ordures dans les applications à faible latence.
Stabilité à long terme : permet de maintenir des performances stables dans les services à long terme en gérant la mémoire sur des périodes prolongées. Efficacité des ressources : peut conserver les ressources processeur et mémoire dans des environnements limités aux ressources.
Maintenance réduite : réduit le besoin de gestion manuelle de la mémoire, ce qui simplifie la maintenance. Contrôle de mémoire manuelle : fournit un contrôle précis de la mémoire pour les applications spécialisées.
Comportement prévisible : contribue au comportement cohérent et prévisible de l’application. Adapté aux processus de courte durée : réduit la surcharge de la gestion des déchets pour les processus transitoires ou éphémères.

Pour plus d’informations sur les considérations relatives aux performances, consultez Server GC. Pour plus d’informations sur la configuration du serveur GC, consultez les exemples de configuration de GC du serveur.

Classe Ouvrier

Quant au Workermodèle, il fournit une implémentation simple.

namespace App.WorkerService;

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

La classe précédente Worker est une sous-classe de BackgroundService, qui implémente IHostedService. Le BackgroundService est un abstract class qui nécessite qu'une sous-classe implémente BackgroundService.ExecuteAsync(CancellationToken). Dans l’implémentation du modèle, la boucle ExecuteAsync s'exécute une fois par seconde, journalisant la date et l’heure actuelles jusqu’à ce que le processus soit signalé pour l’annulation.

Fichier projet

Le modèle Worker s’appuie sur le fichier Sdkprojet suivant :

<Project Sdk="Microsoft.NET.Sdk.Worker">

Pour plus d’informations, consultez kits de développement logiciel pour projets .NET.

Paquet NuGet

Une application basée sur le modèle Worker utilise le Microsoft.NET.Sdk.Worker Kit de développement logiciel (SDK) et possède une référence de package explicite au package Microsoft.Extensions.Hosting .

Conteneurs et adaptabilité du cloud

Avec la plupart des charges de travail .NET modernes, les conteneurs sont une option viable. Lorsque vous créez un service de longue durée à partir du modèle Worker dans Visual Studio, vous pouvez choisir le support de Docker. Cela crée un fichier Dockerfile qui conteneurise votre application .NET. Un fichier Dockerfile est un ensemble d’instructions pour générer une image. Pour les applications .NET, le fichier Dockerfile se trouve généralement à la racine du répertoire en regard d’un fichier de solution.

# See https://aka.ms/containerfastmode to understand how Visual Studio uses this
# Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:8.0@sha256:e6b552fd7a0302e4db30661b16537f7efcdc0b67790a47dbf67a5e798582d3a5 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build
WORKDIR /src
COPY ["background-service/App.WorkerService.csproj", "background-service/"]
RUN dotnet restore "background-service/App.WorkerService.csproj"
COPY . .
WORKDIR "/src/background-service"
RUN dotnet build "App.WorkerService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "App.WorkerService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "App.WorkerService.dll"]

Les étapes dockerfile précédentes sont les suivantes :

  • Définition de l’image de base à partir de mcr.microsoft.com/dotnet/runtime:8.0 comme alias base.
  • Modification du répertoire de travail en /app.
  • Définition de l’alias build à partir de l’image mcr.microsoft.com/dotnet/sdk:8.0 .
  • Modification du répertoire de travail sur /src.
  • Copie du contenu et publication de l’application .NET :
    • L’application est publiée à l’aide de la dotnet publish commande.
  • Relayer l’image du Kit de développement logiciel (SDK) .NET à partir de mcr.microsoft.com/dotnet/runtime:8.0 (l’alias base ).
  • Copie de la sortie de la build publiée depuis /publish.
  • Définition du point d’entrée, qui délègue à dotnet App.BackgroundService.dll.

Conseil / Astuce

Le MCR mcr.microsoft.com signifie « Microsoft Container Registry » et est le catalogue de conteneurs syndiqué de Microsoft à partir du hub Docker officiel. L’article du catalogue de conteneurs des syndicates Microsoft contient des détails supplémentaires.

Lorsque vous ciblez Docker en tant que stratégie de déploiement pour votre service Worker .NET, vous devez prendre en compte quelques points dans le fichier projet :

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WorkerService</RootNamespace>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
  </ItemGroup>
</Project>

Dans le fichier projet précédent, l’élément <DockerDefaultTargetOS> spécifie Linux comme cible. Pour cibler des conteneurs Windows, utilisez Windows à la place. Le Microsoft.VisualStudio.Azure.Containers.Tools.Targets package NuGet est automatiquement ajouté en tant que référence de package lorsque la prise en charge de Docker est sélectionnée à partir du modèle.

Pour plus d’informations sur Docker avec .NET, consultez Tutoriel : Conteneuriser une application .NET. Pour plus d’informations sur le déploiement sur Azure, consultez Tutoriel : Déployer un service Worker sur Azure.

Important

Si vous souhaitez tirer parti des secrets utilisateur avec le modèle Worker, vous devrez référencer explicitement le Microsoft.Extensions.Configuration.UserSecrets package NuGet.

Extensibilité du service hébergé

L'interface IHostedService définit deux méthodes :

Ces deux méthodes servent de méthodes de cycle de vie : elles sont appelées respectivement lors du démarrage de l’hôte et de l’arrêt des événements.

Remarque

En cas de substitution soit des méthodes StartAsync, soit des méthodes StopAsync, vous devez appeler et exécuter la méthode de classe await pour vous assurer que le service démarre et/ou s’arrête correctement.

Important

L’interface sert de contrainte de paramètre de type générique sur la AddHostedService<THostedService>(IServiceCollection) méthode d’extension, ce qui signifie que seules les implémentations sont autorisées. Vous êtes libre d’utiliser la sous-classe fournie BackgroundService ou d’implémenter entièrement votre propre classe.

Achèvement du signal

Dans les scénarios les plus courants, vous n’avez pas besoin de signaler explicitement l’achèvement d’un service hébergé. Lorsque l’hôte démarre les services, ils sont conçus pour s’exécuter jusqu’à ce que l’hôte soit arrêté. Dans certains scénarios, toutefois, vous devrez peut-être signaler l’achèvement de l’ensemble de l’application hôte une fois le service terminé. Pour signaler l’achèvement, tenez compte de la classe suivante Worker :

namespace App.SignalCompletionService;

public sealed class Worker(
    IHostApplicationLifetime hostApplicationLifetime,
    ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // TODO: implement single execution logic here.
        logger.LogInformation(
            "Worker running at: {Time}", DateTimeOffset.Now);

        await Task.Delay(1_000, stoppingToken);

        // When completed, the entire app host will stop.
        hostApplicationLifetime.StopApplication();
    }
}

Dans le code précédent, la BackgroundService.ExecuteAsync(CancellationToken) méthode n’effectue pas de boucle et quand elle est terminée, elle appelle IHostApplicationLifetime.StopApplication().

Important

Cela indiquera à l’hôte qu’il doit s’arrêter, et sans cet appel de StopApplication, l’hôte continuera à s’exécuter indéfiniment. Si vous envisagez d’exécuter un service hébergé de courte durée (exécuter une fois le scénario) et que vous souhaitez utiliser le modèle Worker, vous devez appeler StopApplication pour signaler à l’hôte qu’il s’arrête.

Pour plus d’informations, consultez :

Autre approche

Pour une application de courte durée qui a besoin d’injection de dépendances, de journalisation et de configuration, utilisez l’hôte générique .NET au lieu du modèle Worker. Cela vous permet d’utiliser ces fonctionnalités sans la Worker classe. Un exemple simple d’une application à courte durée d’utilisation de l’hôte générique peut définir un fichier projet comme suit :

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ShortLived.App</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
  </ItemGroup>
</Project>

Il est possible que la classe Program ressemble à ce qui suit :

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<JobRunner>();

using var host = builder.Build();

try
{
    var runner = host.Services.GetRequiredService<JobRunner>();

    await runner.RunAsync();

    return 0; // success
}
catch (Exception ex)
{
    var logger = host.Services.GetRequiredService<ILogger<Program>>();
    
    logger.LogError(ex, "Unhandled exception occurred during job execution.");

    return 1; // failure
}

Le code précédent crée un JobRunner service, qui est une classe personnalisée qui contient la logique de l’exécution du travail. La RunAsync méthode est appelée sur le JobRunner, et si elle se termine correctement, l’application retourne 0. Si une exception non gérée se produit, elle enregistre l’erreur et retourne 1.

Dans ce scénario simple, la JobRunner classe peut ressembler à ceci :

using Microsoft.Extensions.Logging;

internal sealed class JobRunner(ILogger<JobRunner> logger)
{
    public async Task RunAsync()
    {
        logger.LogInformation("Starting job...");

        // Simulate work
        await Task.Delay(1000);

        // Simulate failure
        // throw new InvalidOperationException("Something went wrong!");

        logger.LogInformation("Job completed successfully.");
    }
}

Vous devrez évidemment ajouter une logique réelle à la RunAsync méthode, mais cet exemple montre comment utiliser l’hôte générique pour une application de courte durée sans avoir besoin d’une Worker classe, et sans avoir besoin de signaler explicitement l’achèvement de l’hôte.

Voir aussi