Sdílet prostřednictvím


Vytvoření služby systému Windows pomocí BackgroundService

Vývojáři rozhraní .NET Framework jsou pravděpodobně obeznámeni s aplikacemi služby pro Windows. Před .NET Core a .NET 5+ mohou vývojáři, kteří se spoléhali na rozhraní .NET Framework, vytvářet služby Windows k provádění úloh na pozadí nebo spouštět dlouhotrvající procesy. Tato funkce je stále dostupná a můžete vytvořit služby pracovního procesu, které běží jako služba systému Windows.

V tomto kurzu se naučíte:

  • Publikujte pracovní aplikaci .NET jako jeden spustitelný soubor.
  • Vytvořte službu systému Windows.
  • Vytvořte BackgroundService aplikaci jako službu pro Windows.
  • Spusťte a zastavte službu systému Windows.
  • Zobrazení protokolů událostí
  • Odstraňte službu systému Windows.

Rada

Zdrojový kód "Pracovní procesy v .NET" je k dispozici v prohlížeči ukázek ke stažení. Další informace najdete v tématu Prohlédněte si ukázky kódu: pracovníky v .NET.

Důležitý

Instalace sady .NET SDK také nainstaluje Microsoft.NET.Sdk.Worker a pracovní šablonu. Jinými slovy, po instalaci sady .NET SDK můžete vytvořit nový pracovní proces pomocí příkazu dotnet new worker. Pokud používáte Visual Studio, šablona se skryje, dokud se nenainstaluje volitelná ASP.NET a úloha vývoje webu.

Požadavky

Vytvoření nového projektu

Pokud chcete vytvořit nový projekt Služby pracovního procesu pomocí sady Visual Studio, vyberte Soubor>Nový projekt>.... V dialogovém okně Vytvořit nový projekt vyhledejte "Pracovní služba" a vyberte šablonu pracovní služby. Pokud raději použijete .NET CLI, otevřete svůj oblíbený terminál v pracovním adresáři. Spusťte příkaz dotnet new a nahraďte <Project.Name> názvem požadovaného projektu.

dotnet new worker --name <Project.Name>

Další informace o příkazu projektu nové pracovní služby .NET CLI najdete viz dotnet new worker.

Rada

Pokud používáte Visual Studio Code, můžete z integrovaného terminálu spustit příkazy .NET CLI. Další informace naleznete v tématu Visual Studio Code: Integrovaný terminál.

Instalace balíčku NuGet

Pokud chcete spolupracovat s nativními službami Windows z implementací .NET IHostedService, budete muset nainstalovat balíček Microsoft.Extensions.Hosting.WindowsServices NuGet.

Pokud chcete tuto instalaci nainstalovat ze sady Visual Studio, použijte dialogové okno Spravovat balíčky NuGet.... Vyhledejte "Microsoft.Extensions.Hosting.WindowsServices" a nainstalujte ho. Pokud byste raději použili .NET CLI, spusťte příkaz dotnet package add:

dotnet package add Microsoft.Extensions.Hosting.WindowsServices

Další informace o příkazu pro přidání balíčku rozhraní .NET CLI najdete v tématu dotnet package add.

Po úspěšném přidání balíčků by teď soubor projektu měl obsahovat následující odkazy na balíčky:

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

Aktualizace souboru projektu

Tento projekt pracovníka využívá referenční typy C# s možností hodnoty null. Pokud je chcete povolit pro celý projekt, aktualizujte soubor projektu odpovídajícím způsobem:

<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.3" />
    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.3" />
  </ItemGroup>
</Project>

Změny v předchozím souboru projektu přidávají uzel <Nullable>enable<Nullable>. Další informace naleznete v tématu Nastavení nulovatelného kontextu.

Vytvoření služby

Přidejte do projektu novou třídu s názvem JokeService.csa nahraďte její obsah následujícím kódem jazyka C#:

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);

Předchozí zdrojový kód služby vtipu odhaluje jedinou funkci, a to metodu GetJoke. Jedná se o string návratovou metodu, která představuje náhodný programovací vtip. Pole _jokes s oborem třídy slouží k ukládání seznamu vtipů. Náhodný vtip je vybrán ze seznamu a vrácen.

Přepište třídu Worker

Nahraďte existující Worker ze šablony následujícím kódem jazyka C# a přejmenujte soubor na 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);
        }
    }
}

V předchozím kódu se JokeService vloží spolu s ILogger. Obě jsou k dispozici pro třídu jako pole. V metodě ExecuteAsync služba pro vtipy požádá o vtip a zapíše ho do protokolu. V tomto případě je protokolovací nástroj implementován protokolem událostí systému Windows - Microsoft.Extensions.Logging.EventLog.EventLogLoggerProvider. Protokoly se zapisují do Prohlížeče událostí a jsou k dispozici pro zobrazení.

Poznámka

Ve výchozím nastavení je závažnost protokolu událostíWarning. To je možné nakonfigurovat, ale pro demonstrační účely WindowsBackgroundService logy pomocí rozšiřovací metody LogWarning. Pokud chcete konkrétně cílit na úroveň EventLog, přidejte do nastavení aplikace položku. {Environment}.jsonnebo zadejte hodnotu EventLogSettings.Filter.

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

Další informace o konfiguraci úrovní protokolu naleznete v tématu Zprostředkovatelé protokolování v .NET: Konfigurace protokolu událostí systému Windows.

Přepište třídu Program

Nahraďte obsah souboru šablony Program.cs následujícím kódem jazyka C#:

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();

Metoda rozšíření AddWindowsService nakonfiguruje aplikaci tak, aby fungovala jako služba Systému Windows. Název služby je nastavený na ".NET Joke Service". Hostovaná služba je registrovaná pro injektování závislostí.

Další informace o registraci služeb naleznete v tématu Injektáž závislostí v rozhraní .NET.

Publikování aplikace

Pokud chcete vytvořit aplikaci .NET Worker Service jako službu systému Windows, doporučujeme aplikaci publikovat jako spustitelný soubor. Je méně náchylné k chybám mít samostatný spustitelný soubor, protože neexistují žádné závislé soubory kolem systému souborů. Můžete ale zvolit jiný způsob publikování, což je naprosto přijatelné, pokud vytvoříte *.exe soubor, který může být cílem Správce řízení služeb systému Windows.

Důležitý

Alternativním přístupem k publikování je sestavení *.dll (místo *.exe) a při instalaci publikované aplikace pomocí Správce řízení služeb systému Windows delegujete úkol na rozhraní příkazového řádku .NET a předáte knihovnu DLL. Další informace najdete v tématu .NET CLI: příkaz 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.3" />
    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.3" />
  </ItemGroup>
</Project>

Předchozí zvýrazněné řádky souboru projektu definují následující chování:

  • <OutputType>exe</OutputType>: Vytvoří konzolovou aplikaci.
  • <PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>: Povolí publikování s jedním souborem.
  • <RuntimeIdentifier>win-x64</RuntimeIdentifier>: Určuje RID z win-x64.
  • <PlatformTarget>x64</PlatformTarget>: Zadejte procesor 64bitové cílové platformy.

Pokud chcete aplikaci publikovat ze sady Visual Studio, můžete vytvořit profil publikování, který je trvalý. Profil publikování je založený na jazyce XML a má příponu souboru .pubxml. Visual Studio používá tento profil k implicitní publikování aplikace, zatímco pokud používáte .NET CLI, musíte explicitně zadat profil publikování, který se má použít.

Klikněte pravým tlačítkem myši na projekt v průzkumníku řešení, a vyberte Publikovat. Potom vyberte Přidat publikační profil a vytvořte profil. V dialogovém okně Publikovat vyberte složku jako svou cílovou.

Dialogové okno publikování v aplikaci Visual Studio

Ponechte základní Umístění, poté vyberte Dokončit. Po vytvoření profilu vyberte Zobrazit všechna nastavenía ověřte nastavení profilu.

nastavení profilu sady Visual Studio

Ujistěte se, že jsou zadána následující nastavení:

  • režim nasazení: samostatné
  • Vytvořit jeden soubor: zaškrtnuto
  • povolení kompilace ReadyToRun: zaškrtnuto
  • Oříznutí nepoužívaných sestavení (ve verzi Preview): nezaškrtnuto

Nakonec vyberte Publikovat. Aplikace se zkompiluje a výsledný soubor .exe se publikuje do výstupního adresáře /publish.

Případně můžete k publikování aplikace použít rozhraní příkazového řádku .NET:

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

Další informace najdete v tématu dotnet publish.

Důležitý

Pokud se v .NET 6 pokusíte aplikaci ladit pomocí nastavení <PublishSingleFile>true</PublishSingleFile>, nebudete moct aplikaci ladit. Další informace najdete v tématu Nelze se připojit k CoreCLR při ladění aplikace .NET 6 PublishSingleFile.

Vytvoření služby systému Windows

Pokud neznáte powershell a raději byste pro službu vytvořili instalační program, přečtěte si téma Vytvoření instalačního programu služby systému Windows. V opačném případě vytvořte službu Systému Windows pomocí nativního příkazu Windows Service Control Manager (sc.exe) create. Spusťte PowerShell jako správce.

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

Rada

Pokud potřebujete změnit kořenový adresář obsahu v konfiguraci hostitele, můžete jej předat jako argument příkazové řádky při specifikaci binpath:

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

Zobrazí se výstupní zpráva:

[SC] CreateService SUCCESS

Pro více informací viz sc.exe vytvoření.

Konfigurace služby systému Windows

Po vytvoření služby ji můžete volitelně nakonfigurovat. Pokud máte výchozí nastavení služby v pořádku, přejděte do části Ověření funkčnosti služby.

Služby Windows poskytují možnosti konfigurace obnovení. Aktuální konfiguraci můžete dotazovat pomocí příkazu sc.exe qfailure "<Service Name>" (kde <Service Name> je název vaší služby) a přečíst aktuální hodnoty konfigurace obnovení:

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

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

Příkaz zobrazí výstup konfigurace obnovení, což jsou výchozí hodnoty, protože ještě nejsou nakonfigurované.

dialogové okno vlastností konfigurace obnovení služby Systému Windows.

Pokud chcete nakonfigurovat obnovení, použijte sc.exe failure "<Service Name>", kde <Service Name> je název vaší služby:

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

Rada

Pokud chcete nakonfigurovat možnosti obnovení, musí být vaše relace terminálu spuštěna s oprávněními správce.

Po úspěšné konfiguraci můžete znovu zadat dotaz na hodnoty pomocí příkazu sc.exe qfailure "<Service Name>":

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.

Zobrazí se nakonfigurované hodnoty restartování.

dialogové okno vlastností konfigurace obnovení služby Windows s povoleným restartováním.

Možnosti obnovy služby a instance .NET BackgroundService

V rozhraní .NET 6 do .NET byly přidány nové chování zpracování výjimek hostování,. Výčet BackgroundServiceExceptionBehavior byl přidán do oboru názvů Microsoft.Extensions.Hosting a slouží k určení chování služby při vyvolání výjimky. V následující tabulce jsou uvedené dostupné možnosti:

Možnost Popis
Ignore Ignorovat výjimky vyvolané v BackgroundService.
StopHost Při vyvolání neošetřené výjimky se IHost zastaví.

Výchozí chování před rozhraním .NET 6 je Ignore, což vedlo k zombie procesům (běžící proces, který nic nedělal). U .NET 6 je výchozí chování StopHost, což vede k zastavení hostitele při vyvolání výjimky. Ale zastaví se čistě, což znamená, že správce služeb systému Windows nerestartuje službu. Chcete-li správně povolit restartování služby, můžete volat Environment.Exit s nenulovým ukončovacím kódem. Zvažte následující zvýrazněný blok catch:

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);
        }
    }
}

Ověření funkčnosti služby

Pokud chcete zobrazit aplikaci vytvořenou jako službu Systému Windows, otevřete Services . Vyberte klávesu Windows (nebo Ctrl + Esc) a vyhledejte "Služby". V aplikaci Services byste měli být schopni vaši službu najít podle jejího názvu.

Důležitý

Ve výchozím nastavení nemůžou běžní uživatelé (bez oprávnění správce) spravovat služby systému Windows. Pokud chcete ověřit, že tato aplikace funguje podle očekávání, budete muset použít účet správce.

uživatelské rozhraní Služby.

Pokud chcete ověřit, že služba funguje podle očekávání, musíte:

  • Spusťte službu
  • Zobrazení protokolů
  • Zastavení služby

Důležitý

Pokud chcete ladit aplikaci, ujistěte se, že se nepokoušíte ladit spustitelný soubor, který je aktivně spuštěný v rámci procesu Windows Services.

Nelze spustit program.

Spuštění služby systému Windows

Ke spuštění služby systému Windows použijte příkaz sc.exe start:

sc.exe start ".NET Joke Service"

Zobrazí se výstup podobný následujícímu:

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

Stav služby přejde ze stavu START_PENDING do stavu , spuštěno.

Zobrazení protokolů

Pokud chcete zobrazit protokoly, otevřete prohlížeče událostí. Vyberte klávesu Windows (nebo Ctrl + Esc) a vyhledejte "Event Viewer". Vyberte uzel Prohlížeč událostí (místní)>Protokoly systému Windows>Aplikace. Měli byste vidět položku na úrovni varování , která má zdroj odpovídající oboru názvů aplikací. Poklikejte na položku, nebo klikněte pravým tlačítkem myši a vyberte Vlastnosti události, abyste zobrazili podrobnosti.

dialogové okno Vlastnosti události s podrobnostmi protokolovanými ze služby

Po zobrazení protokolů v protokolu událostí byste měli službu zastavit. Je navržený tak, aby protokoloval náhodný vtip jednou za minutu. Jedná se o úmyslné chování, ale není praktické pro produkční služby.

Zastavení služby systému Windows

Pokud chcete službu Systému Windows zastavit, použijte příkaz sc.exe stop:

sc.exe stop ".NET Joke Service"

Zobrazí se výstup podobný následujícímu:

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

Stav služby se změní z STOP_PENDING na Zastaveno.

Odstranění služby systému Windows

Pokud chcete odstranit službu Systému Windows, použijte příkaz delete nativního správce řízení služeb systému Windows (sc.exe). Spusťte PowerShell jako správce.

Důležitý

Pokud služba není ve stavu Zastaveno, nebude okamžitě odstraněna. Před vydáním příkazu delete se ujistěte, že je služba zastavená.

sc.exe delete ".NET Joke Service"

Zobrazí se výstupní zpráva:

[SC] DeleteService SUCCESS

Další informace najdete v tématu sc.exe odstranění.

Viz také

Další