Partager via


Créer un service Windows à l’aide de BackgroundService

Les développeurs .NET Framework sont probablement familiarisés avec les applications de service Windows. Avant .NET Core et .NET 5+, les développeurs qui s’appuyaient sur .NET Framework pourraient créer des services Windows pour effectuer des tâches en arrière-plan ou exécuter des processus de longue durée. Cette fonctionnalité est toujours disponible et vous pouvez créer des services Worker qui s’exécutent en tant que service Windows.

Dans ce tutoriel, vous apprendrez comment le faire :

  • Publiez une application worker .NET en tant que fichier exécutable unique.
  • Créez un service Windows.
  • Créez l’application BackgroundService en tant que service Windows.
  • Démarrez et arrêtez le service Windows.
  • Affichez les journaux des événements.
  • Supprimez le service Windows.

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.

Importante

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.

Conditions préalables

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 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 / 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é.

Installer le package NuGet

Pour interagir avec les services Windows natifs depuis les implémentations .NET IHostedService, vous devez installer le paquet NuGet Microsoft.Extensions.Hosting.WindowsServices.

Pour l’installer à partir de Visual Studio, utilisez la boîte de dialogue Gérer les packages NuGet . Recherchez « Microsoft.Extensions.Hosting.WindowsServices » et installez-le. Si vous préférez utiliser l’interface CLI .NET, exécutez la commande suivante. (Si vous utilisez une version du Kit de développement logiciel (SDK) de .NET 9 ou antérieure, utilisez plutôt le dotnet add package formulaire.)

dotnet package add Microsoft.Extensions.Hosting.WindowsServices

Pour plus d’informations, consultez dotnet package add.

Une fois les packages ajoutés, votre fichier projet doit maintenant contenir les références de package suivantes :

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.10" />
  <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.10" />
</ItemGroup>

Mettre à jour le fichier projet

Ce projet de travail utilise les types de référence nullables de C#. Pour les activer pour l’ensemble du projet, mettez à jour le fichier projet en conséquence :

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

  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WindowsService</RootNamespace>
  </PropertyGroup>

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

Les modifications apportées au fichier de projet précédent ajoutent le nœud <Nullable>enable<Nullable>. Pour plus d’informations, consultez Définition du contexte nullable.

Créer le service

Ajoutez une nouvelle classe au projet nommé JokeService.cs et remplacez son contenu par le code C# suivant :

namespace App.WindowsService;

public sealed class JokeService
{
    public string GetJoke()
    {
        Joke joke = _jokes.ElementAt(
            Random.Shared.Next(_jokes.Count));

        return $"{joke.Setup}{Environment.NewLine}{joke.Punchline}";
    }

    // Programming jokes borrowed from:
    // https://github.com/eklavyadev/karljoke/blob/main/source/jokes.json
    private readonly HashSet<Joke> _jokes = new()
    {
        new Joke("What's the best thing about a Boolean?", "Even if you're wrong, you're only off by a bit."),
        new Joke("What's the object-oriented way to become wealthy?", "Inheritance"),
        new Joke("Why did the programmer quit their job?", "Because they didn't get arrays."),
        new Joke("Why do programmers always mix up Halloween and Christmas?", "Because Oct 31 == Dec 25"),
        new Joke("How many programmers does it take to change a lightbulb?", "None that's a hardware problem"),
        new Joke("If you put a million monkeys at a million keyboards, one of them will eventually write a Java program", "the rest of them will write Perl"),
        new Joke("['hip', 'hip']", "(hip hip array)"),
        new Joke("To understand what recursion is...", "You must first understand what recursion is"),
        new Joke("There are 10 types of people in this world...", "Those who understand binary and those who don't"),
        new Joke("Which song would an exception sing?", "Can't catch me - Avicii"),
        new Joke("Why do Java programmers wear glasses?", "Because they don't C#"),
        new Joke("How do you check if a webpage is HTML5?", "Try it out on Internet Explorer"),
        new Joke("A user interface is like a joke.", "If you have to explain it then it is not that good."),
        new Joke("I was gonna tell you a joke about UDP...", "...but you might not get it."),
        new Joke("The punchline often arrives before the set-up.", "Do you know the problem with UDP jokes?"),
        new Joke("Why do C# and Java developers keep breaking their keyboards?", "Because they use a strongly typed language."),
        new Joke("Knock-knock.", "A race condition. Who is there?"),
        new Joke("What's the best part about TCP jokes?", "I get to keep telling them until you get them."),
        new Joke("A programmer puts two glasses on their bedside table before going to sleep.", "A full one, in case they gets thirsty, and an empty one, in case they don’t."),
        new Joke("There are 10 kinds of people in this world.", "Those who understand binary, those who don't, and those who weren't expecting a base 3 joke."),
        new Joke("What did the router say to the doctor?", "It hurts when IP."),
        new Joke("An IPv6 packet is walking out of the house.", "He goes nowhere."),
        new Joke("3 SQL statements walk into a NoSQL bar. Soon, they walk out", "They couldn't find a table.")
    };
}

readonly record struct Joke(string Setup, string Punchline);

Le code source du service joke précédent expose un seul élément de fonctionnalité, la GetJoke méthode. Il s’agit d’une string méthode de retour qui représente une blague de programmation aléatoire. La variable de champ _jokes à portée de classe est utilisée pour stocker la liste des blagues. Une blague aléatoire est sélectionnée dans la liste et retournée.

Réécrire la Worker classe

Remplacez l’existant Worker à partir du modèle par le code C# suivant, puis renommez le fichier en WindowsBackgroundService.cs :

namespace App.WindowsService;

public sealed class WindowsBackgroundService(
    JokeService jokeService,
    ILogger<WindowsBackgroundService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string joke = jokeService.GetJoke();
                logger.LogWarning("{Joke}", joke);

                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // When the stopping token is canceled, for example, a call made from services.msc,
            // we shouldn't exit with a non-zero exit code. In other words, this is expected...
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "{Message}", ex.Message);

            // Terminates this process and returns an exit code to the operating system.
            // This is required to avoid the 'BackgroundServiceExceptionBehavior', which
            // performs one of two scenarios:
            // 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
            // 2. When set to "StopHost": will cleanly stop the host, and log errors.
            //
            // In order for the Windows Service Management system to leverage configured
            // recovery options, we need to terminate the process with a non-zero exit code.
            Environment.Exit(1);
        }
    }
}

Dans le code précédent, un JokeService est injecté avec un ILogger. Les deux sont disponibles pour la classe en tant que champs. Dans la ExecuteAsync méthode, le service de blague demande une blague et l’écrit dans l’enregistreur d’événements. Dans ce cas, l’enregistreur d’événements est implémenté par le journal des événements Windows - Microsoft.Extensions.Logging.EventLog.EventLogLoggerProvider. Les journaux sont écrits et disponibles pour l’affichage dans l’observateur d’événements.

Remarque

Par défaut, la gravité du journal des événements est Warning. Cela peut être configuré, mais à des fins de démonstration, WindowsBackgroundService journalise la méthode d’extension LogWarning. Pour cibler spécifiquement le EventLog niveau, ajoutez une entrée dans appsettings.{ Environment}.jsonou fournissez une EventLogSettings.Filter valeur.

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning"
    },
    "EventLog": {
      "SourceName": "The Joke Service",
      "LogName": "Application",
      "LogLevel": {
        "Microsoft": "Information",
        "Microsoft.Hosting.Lifetime": "Information"
      }
    }
  }
}

Pour plus d’informations sur la configuration des niveaux de journalisation, consultez Les fournisseurs de journalisation dans .NET : Configurer Windows EventLog.

Réécrire la Program classe

Remplacez le modèle Program.cs contenu du fichier par le code C# suivant :

using App.WindowsService;
using Microsoft.Extensions.Logging.Configuration;
using Microsoft.Extensions.Logging.EventLog;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(options =>
{
    options.ServiceName = ".NET Joke Service";
});

LoggerProviderOptions.RegisterProviderOptions<
    EventLogSettings, EventLogLoggerProvider>(builder.Services);

builder.Services.AddSingleton<JokeService>();
builder.Services.AddHostedService<WindowsBackgroundService>();

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

La AddWindowsService méthode d’extension configure l’application pour qu’elle fonctionne en tant que service Windows. Le nom du service est défini sur ".NET Joke Service". Le service hébergé est inscrit pour l’injection de dépendances.

Pour plus d’informations sur l’enregistrement des services, consultez Injection de Dépendances dans .NET.

Publier l’application

Pour créer l’application .NET Worker Service en tant que service Windows, il est recommandé de publier l’application en tant qu’exécutable de fichier unique. Il est moins sujet aux erreurs d'avoir un exécutable autonome, car il n’y a pas de fichiers dépendants dispersés dans le système de fichiers. Toutefois, vous pouvez choisir une autre modalité de publication, qui est parfaitement acceptable, tant que vous créez un fichier *.exe qui peut être ciblé par le Gestionnaire de contrôle de service Windows.

Importante

Une autre approche de publication consiste à générer le *.dll (au lieu d’un *.exe), et lorsque vous installez l’application publiée à l’aide du Gestionnaire de contrôle de service Windows, déléguez à l’interface CLI .NET pour passer la DLL. Pour plus d’informations, consultez l’interface CLI .NET : commande dotnet.

sc.exe create ".NET Joke Service" binpath= "C:\Path\To\dotnet.exe C:\Path\To\App.WindowsService.dll"
<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WindowsService</RootNamespace>
    <OutputType>exe</OutputType>
    <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
    <RuntimeIdentifier>win-x64</RuntimeIdentifier>
    <PlatformTarget>x64</PlatformTarget>
  </PropertyGroup>

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

Les lignes mises en surbrillance précédentes du fichier projet définissent les comportements suivants :

  • <OutputType>exe</OutputType>: crée une application console.
  • <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>: active la publication à fichier unique.
  • <RuntimeIdentifier>win-x64</RuntimeIdentifier>: spécifie le RID de win-x64.
  • <PlatformTarget>x64</PlatformTarget>: spécifiez le processeur de plateforme cible de 64 bits.

Pour publier l’application à partir de Visual Studio, vous pouvez créer un profil de publication persistant. Le profil de publication est basé sur XML et a l’extension de fichier .pubxml . Visual Studio utilise ce profil pour publier l’application implicitement, alors que si vous utilisez l’interface CLI .NET, vous devez spécifier explicitement le profil de publication pour qu’il soit utilisé.

Cliquez avec le bouton droit sur le projet dans l’Explorateur de solutions, puis sélectionnez Publier. Ensuite, sélectionnez Ajouter un profil de publication pour créer un profil. Dans la boîte de dialogue Publier , sélectionnez Dossier comme cible.

Boîte de dialogue Publier de Visual Studio

Laissez l’emplacement par défaut, puis sélectionnez Terminer. Une fois le profil créé, sélectionnez Afficher tous les paramètres et vérifiez vos paramètres de profil.

Paramètres du profil Visual Studio

Vérifiez que les paramètres suivants sont spécifiés :

  • Mode de déploiement : autonome
  • Produire un fichier unique : vérifié
  • Activer la compilation ReadyToRun : cochée
  • Réduire les assemblys inutilisés (en préversion) : décoché

Enfin, sélectionnez Publier. L’application est compilée et le fichier .exe résultant est publié dans le répertoire de sortie /publish .

Vous pouvez également utiliser l’interface CLI .NET pour publier l’application :

dotnet publish --output "C:\custom\publish\directory"

Pour plus d’informations, consultez dotnet publish.

Importante

Avec .NET 6, si vous tentez de déboguer l’application avec le <PublishSingleFile>true</PublishSingleFile> paramètre, vous ne pourrez pas déboguer l’application. Pour plus d’informations, consultez Impossible d’attacher à CoreCLR lors du débogage d’une application .NET 6 « PublishSingleFile ».

Créer le service Windows

Si vous ne connaissez pas l’utilisation de PowerShell et que vous préférez créer un programme d’installation pour votre service, consultez Créer un programme d’installation de service Windows. Sinon, pour créer le service Windows, utilisez la commande de création native du Gestionnaire de contrôle de service Windows (sc.exe). Exécutez PowerShell en tant qu’administrateur.

sc.exe create ".NET Joke Service" binpath= "C:\Path\To\App.WindowsService.exe"

Conseil / Astuce

Si vous devez modifier la racine de contenu de la configuration de l’hôte, vous pouvez la passer en tant qu’argument de ligne de commande lors de la spécification des binpathéléments suivants :

sc.exe create "Svc Name" binpath= "C:\Path\To\App.exe --contentRoot C:\Other\Path"

Un message de sortie s’affiche :

[SC] CreateService SUCCESS

Pour plus d’informations, consultez sc.exe créer.

Configurer le service Windows

Une fois le service créé, vous pouvez éventuellement le configurer. Si vous êtes satisfait des valeurs par défaut du service, passez à la section Vérifier la fonctionnalité du service.

Les services Windows fournissent des options de configuration de récupération. Vous pouvez interroger la configuration actuelle en utilisant la commande sc.exe qfailure "<Service Name>" (où sc.exe qfailure "<Service Name>" est le nom de vos services) pour consulter les valeurs actuelles de configuration de récupération :

sc qfailure ".NET Joke Service"
[SC] QueryServiceConfig2 SUCCESS

SERVICE_NAME: .NET Joke Service
        RESET_PERIOD (in seconds)    : 0
        REBOOT_MESSAGE               :
        COMMAND_LINE                 :

La commande génère la configuration de récupération, qui est les valeurs par défaut, car elles n’ont pas encore été configurées.

Boîte de dialogue des propriétés de configuration de récupération du service Windows.

Pour configurer la récupération, utilisez l’emplacement sc.exe failure "<Service Name>"<Service Name> se trouve le nom de votre service :

sc.exe failure ".NET Joke Service" reset= 0 actions= restart/60000/restart/60000/run/1000
[SC] ChangeServiceConfig2 SUCCESS

Conseil / Astuce

Pour configurer les options de récupération, votre session de terminal doit s’exécuter en tant qu’administrateur.

Une fois qu’elle a été correctement configurée, vous pouvez interroger à nouveau les valeurs à l’aide de la sc.exe qfailure "<Service Name>" commande :

sc qfailure ".NET Joke Service"
[SC] QueryServiceConfig2 SUCCESS

SERVICE_NAME: .NET Joke Service
        RESET_PERIOD (in seconds)    : 0
        REBOOT_MESSAGE               :
        COMMAND_LINE                 :
        FAILURE_ACTIONS              : RESTART -- Delay = 60000 milliseconds.
                                       RESTART -- Delay = 60000 milliseconds.
                                       RUN PROCESS -- Delay = 1000 milliseconds.

Vous verrez les valeurs de redémarrage configurées.

Boîte de dialogue des propriétés de configuration de récupération du service Windows avec redémarrage activé.

Options de récupération de service et instances .NET BackgroundService

Avec .NET 6, de nouveaux comportements de gestion des exceptions d’hébergement ont été ajoutés à .NET. L’énumération BackgroundServiceExceptionBehavior a été ajoutée dans l’espace de noms Microsoft.Extensions.Hosting et est utilisée pour spécifier le comportement du service lorsqu’une exception est levée. Le tableau suivant répertorie les options disponibles :

Choix Description
Ignore Ignorez les exceptions levées dans BackgroundService.
StopHost Le IHost est arrêté lorsqu'une exception non gérée est déclenchée.

Le comportement par défaut avant .NET 6 est Ignore, ce qui a entraîné des processus zombies (un processus en cours d’exécution qui n’a rien fait). Avec .NET 6, le comportement par défaut est StopHost, ce qui entraîne l’arrêt de l’hôte lorsqu’une exception est levée. Mais elle s’arrête correctement, ce qui signifie que le système de gestion des services Windows ne redémarre pas le service. Pour autoriser correctement le redémarrage du service, vous pouvez appeler Environment.Exit avec un code de sortie différent de zéro. Tenez compte du bloc mis en surbrillance catch suivant :

namespace App.WindowsService;

public sealed class WindowsBackgroundService(
    JokeService jokeService,
    ILogger<WindowsBackgroundService> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        try
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                string joke = jokeService.GetJoke();
                logger.LogWarning("{Joke}", joke);

                await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
            }
        }
        catch (OperationCanceledException)
        {
            // When the stopping token is canceled, for example, a call made from services.msc,
            // we shouldn't exit with a non-zero exit code. In other words, this is expected...
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "{Message}", ex.Message);

            // Terminates this process and returns an exit code to the operating system.
            // This is required to avoid the 'BackgroundServiceExceptionBehavior', which
            // performs one of two scenarios:
            // 1. When set to "Ignore": will do nothing at all, errors cause zombie services.
            // 2. When set to "StopHost": will cleanly stop the host, and log errors.
            //
            // In order for the Windows Service Management system to leverage configured
            // recovery options, we need to terminate the process with a non-zero exit code.
            Environment.Exit(1);
        }
    }
}

Vérifier la fonctionnalité de service

Pour voir l’application créée en tant que service Windows, ouvrez Services. Sélectionnez la touche Windows (ou Ctrl + Échap), puis recherchez dans « Services ». À partir de l’application Services , vous devez être en mesure de trouver votre service par son nom.

Importante

Par défaut, les utilisateurs standard (non administrateurs) ne peuvent pas gérer les services Windows. Pour vérifier que cette application fonctionne comme prévu, vous devez utiliser un compte d’administrateur.

Interface utilisateur des services.

Pour vérifier que le service fonctionne comme prévu, vous devez :

  • Démarrer le service
  • Afficher les journaux
  • Arrêter le service

Importante

Pour déboguer l’application, vérifiez que vous ne tentez pas de déboguer l’exécutable qui s’exécute activement dans le processus des services Windows.

Impossible de démarrer le programme.

Démarrer le service Windows

Pour démarrer le service Windows, utilisez la sc.exe start commande suivante :

sc.exe start ".NET Joke Service"

Vous verrez une sortie similaire à ce qui suit :

SERVICE_NAME: .NET Joke Service
    TYPE               : 10  WIN32_OWN_PROCESS
    STATE              : 2  START_PENDING
                            (NOT_STOPPABLE, NOT_PAUSABLE, IGNORES_SHUTDOWN)
    WIN32_EXIT_CODE    : 0  (0x0)
    SERVICE_EXIT_CODE  : 0  (0x0)
    CHECKPOINT         : 0x0
    WAIT_HINT          : 0x7d0
    PID                : 37636
    FLAGS

L’état du service passe de START_PENDING à Running(En cours d’exécution).

Afficher les journaux d’activité

Pour afficher les journaux, ouvrez l’Observateur d’événements. Sélectionnez la touche Windows (ou Ctrl + Échap), puis recherchez "Event Viewer". Sélectionnez le nœud Observateur d’événements (local)>>Journaux WindowsApplication. Vous devez voir une entrée de niveau Avertissement avec une source correspondant à l’espace de noms des applications. Double-cliquez sur l’entrée, ou cliquez avec le bouton droit et sélectionnez Propriétés de l’événement pour afficher les détails.

Boîte de dialogue Propriétés de l’événement, avec les détails consignés à partir du service

Après avoir consulté le Journal des événements, vous devez arrêter le service. Il est conçu pour consigner une blague aléatoire une fois par minute. Il s’agit d’un comportement intentionnel, mais n’est pas pratique pour les services de production.

Arrêter le service Windows

Pour arrêter le service Windows, utilisez la sc.exe stop commande suivante :

sc.exe stop ".NET Joke Service"

Vous verrez une sortie similaire à ce qui suit :

SERVICE_NAME: .NET Joke Service
    TYPE               : 10  WIN32_OWN_PROCESS
    STATE              : 3  STOP_PENDING
                            (STOPPABLE, NOT_PAUSABLE, ACCEPTS_SHUTDOWN)
    WIN32_EXIT_CODE    : 0  (0x0)
    SERVICE_EXIT_CODE  : 0  (0x0)
    CHECKPOINT         : 0x0
    WAIT_HINT          : 0x0

L’état du service passe de STOP_PENDING à Arrêté.

Supprimer le service Windows

Pour supprimer le service Windows, utilisez la commande de suppression native du Gestionnaire de contrôle de service Windows (sc.exe). Exécutez PowerShell en tant qu’administrateur.

Importante

Si le service n’est pas dans l’état Arrêté , il ne sera pas immédiatement supprimé. Vérifiez que le service est arrêté avant d’émettre la commande delete.

sc.exe delete ".NET Joke Service"

Un message de sortie s’affiche :

[SC] DeleteService SUCCESS

Pour plus d’informations, consultez sc.exe supprimer.

Voir aussi

Suivant