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.
BackgroundService
Vytvořte 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.
Tip
Všechny ukázkové zdrojové kódy Pracovních procesů v .NET jsou k dispozici v prohlížeči ukázek ke stažení. Další informace najdete v tématu Procházení ukázek kódu: Pracovní procesy v .NET.
Důležité
Instalace sady .NET SDK také nainstaluje Microsoft.NET.Sdk.Worker
šablonu pracovního procesu. 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
- Sada .NET 8.0 SDK nebo novější
- Operační systém Windows
- Integrované vývojové prostředí .NET (IDE)
- Neváhejte používat Visual Studio
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. dotnet new
Spusťte příkaz a nahraďte název požadovaného <Project.Name>
projektu.
dotnet new worker --name <Project.Name>
Další informace o novém příkazu pracovního procesu rozhraní příkazového řádku .NET CLI najdete v tématu dotnet new worker.
Tip
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 Microsoft.Extensions.Hosting.WindowsServices
balíček 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 dotnet add package
příkaz:
dotnet add package Microsoft.Extensions.Hosting.WindowsServices
Další informace o příkazu pro přidání balíčku rozhraní .NET CLI najdete v tématu dotnet add package.
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="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
</ItemGroup>
Aktualizace souboru projektu
Tento pracovní projekt využívá odkazové typy C#s možnou hodnotou 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="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
</ItemGroup>
</Project>
Předchozí soubor projektu změní <Nullable>enable<Nullable>
uzel. Další informace naleznete v tématu Nastavení kontextu s možnou hodnotou null.
Vytvoření služby
Přidejte do projektu novou třídu s názvem JokeService.cs a 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í kód zdroje služby vtipu zveřejňuje jednu část funkčnosti, metodu GetJoke
. Jedná se o návratovou metodu string
, která představuje náhodný programovací vtip. Pole s oborem _jokes
třídy slouží k uložení seznamu vtipů. Náhodný vtip je vybrán ze seznamu a vrácen.
Přepsání Worker
třídy
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 JokeService
se vloží spolu s znakem ILogger
. Obě jsou k dispozici pro třídu jako private readonly
pole. ExecuteAsync
V metodě služba vtip požádá o vtip a zapíše ho do protokolovacího nástroje. V tomto případě je protokolovací nástroj implementován protokolem událostí systému Windows - Microsoft.Extensions.Logging.EventLog.EventLogLogger. Protokoly se zapisují a jsou k dispozici pro zobrazení v Prohlížeč událostí.
Poznámka:
Ve výchozím nastavení je Warningzávažnost protokolu událostí . To je možné nakonfigurovat, ale pro demonstrační účely protokoly WindowsBackgroundService
s metodou LogWarning rozšíření. Pokud chcete konkrétně cílit na EventLog
úroveň, přidejte do appsettings položku.{ Environment}.json nebo zadejte EventLogSettings.Filter hodnotu.
{
"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řepsání Program
třídy
Obsah souboru Program.cs šablony nahraďte 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 AddWindowsService
rozšíření nakonfiguruje aplikaci tak, aby fungovala jako služba systému Windows. Název služby je nastaven na ".NET Joke Service"
hodnotu . Hostovaná služba je zaregistrovaná pro injektáž závislostí.
Další informace o registraci služeb naleznete v tématu Injektáž závislostí v .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 soubor *.exe , 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í knihovny *.dll (místo *.exe) a při instalaci publikované aplikace pomocí Správce řízení služeb systému Windows, který delegujete 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="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
</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 identifikátor RID .win-x64
<PlatformTarget>x64</PlatformTarget>
: Zadejte procesor cílové platformy 64bitové verze.
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 rozhraní příkazového řádku .NET – 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ík řešení a vyberte Publikovat.... Pak vyberte Přidat profil publikování a vytvořte profil. V dialogovém okně Publikovat vyberte jako cíl složku.
Ponechte výchozí umístění a pak vyberte Dokončit. Po vytvoření profilu vyberte Zobrazit všechna nastavení a ověřte nastavení profilu.
Ujistěte se, že jsou zadána následující nastavení:
- Režim nasazení: Samostatné
- Vytvoření jednoho souboru: zaškrtnuto
- Povolit kompilaci 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 na webu dotnet publish
.
Důležité
Pokud se v .NET 6 pokusíte aplikaci ladit pomocí <PublishSingleFile>true</PublishSingleFile>
nastavení, nebudete moct aplikaci ladit. Další informace naleznete v tématu Nejde připojit k CoreCLR při ladění aplikace PublishSingleFile .NET 6.
Vytvoření služby systému Windows
Pokud nejste zvyklí používat 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 Správce řízení služeb systému Windows (sc.exe) create. Spusťte PowerShell jako správce.
sc.exe create ".NET Joke Service" binpath="C:\Path\To\App.WindowsService.exe"
Tip
Pokud potřebujete změnit kořen obsahu konfigurace hostitele, můžete ho předat jako argument příkazového řádku při zadávání binpath
příkazu :
sc.exe create "Svc Name" binpath="C:\Path\To\App.exe --contentRoot C:\Other\Path"
Zobrazí se výstupní zpráva:
[SC] CreateService SUCCESS
Další informace naleznete v tématu sc.exe create.
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í sc.exe qfailure "<Service Name>"
příkazu (where <Service Name>
is your services' name) ke čtení aktuálních hodnot 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é.
Pokud chcete nakonfigurovat obnovení, použijte sc.exe failure "<Service Name>"
místo, 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
Tip
Pokud chcete nakonfigurovat možnosti obnovení, musí vaše relace terminálu běžet jako Správa istrator.
Po úspěšné konfiguraci můžete znovu zadat dotaz na hodnoty pomocí sc.exe qfailure "<Service Name>"
příkazu:
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í.
Možnosti obnovení služby a instance .NET BackgroundService
V rozhraní .NET 6 byly do .NET přidány nové chování zpracování výjimek hostování. Výčet BackgroundServiceExceptionBehavior byl přidán do Microsoft.Extensions.Hosting
oboru názvů 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 IHost vyvolání neošetřené výjimky se zastaví. |
Výchozí chování před .NET 6 je Ignore
, což způsobilo zombie procesy (spuštěný proces, který nic neudělal). U .NET 6 je StopHost
výchozí chování , což vede k zastavení hostitele při vyvolání výjimky. Ale zastaví se čistě, což znamená, že systém pro správu služby systému Windows nerestartuje službu. Chcete-li správně povolit restartování služby, můžete volat Environment.Exit nenulový ukončovací kód. Zvažte následující zvýrazněný catch
blok:
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
Aplikaci vytvořenou jako službu Windows zobrazíte tak, že otevřete služby. Vyberte klávesu Windows (nebo Ctrl + Esc) a vyhledejte v části 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áva.
Pokud chcete ověřit, že služba funguje podle očekávání, musíte:
- Spuštění služby
- Zobrazení protokolů
- Zastavení služby
Důležité
Pokud chcete aplikaci ladit, ujistěte se, že se nepokoušejte ladit spustitelný soubor, který je aktivně spuštěný v rámci procesu služeb systému Windows.
Spuštění služby systému Windows
Ke spuštění služby systému Windows použijte sc.exe start
příkaz:
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 bude mimo provozSTART_PENDING
.
Zobrazení protokolů
Pokud chcete zobrazit protokoly, otevřete Prohlížeč událostí. Vyberte klávesu Windows (nebo Ctrl + Esc) a vyhledejte "Event Viewer"
. Vyberte uzel aplikace Prohlížeč událostí (místní)>Protokoly> systému Windows. Měla by se zobrazit položka na úrovni upozornění se zdrojem, který odpovídá oboru názvů aplikací. Poklikejte na položku nebo klikněte pravým tlačítkem myši a vyberte Vlastnosti události a zobrazte podrobnosti.
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 záměrné 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 přejde z STOP_PENDING
do stavu Zastaveno.
Odstranění služby systému Windows
Chcete-li odstranit službu systému Windows, použijte nativní příkaz Správce řízení služeb systému Windows (sc.exe) delete příkaz. Spusťte PowerShell jako správce.
Důležité
Pokud služba není ve stavu Zastaveno , neodstraní se okamžitě. 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 naleznete v tématu sc.exe delete.
Viz také
- Vytvoření instalačního programu služby systému Windows
- Služby pracovních procesů v .NET
- Vytvoření služby fronty
- Použití vymezených služeb v rámci
BackgroundService
IHostedService
Implementace rozhraní