Skapa Windows-tjänsten med hjälp av BackgroundService
.NET Framework-utvecklare är förmodligen bekanta med Windows Service-appar. Före .NET Core och .NET 5+ kunde utvecklare som förlitade sig på .NET Framework skapa Windows-tjänster för att utföra bakgrundsuppgifter eller köra långvariga processer. Den här funktionen är fortfarande tillgänglig och du kan skapa Arbetartjänster som körs som en Windows-tjänst.
I den här självstudien får du lära dig att:
- Publicera en .NET-arbetsapp som en körbar fil.
- Skapa en Windows-tjänst.
BackgroundService
Skapa appen som en Windows-tjänst.- Starta och stoppa Windows-tjänsten.
- Visa händelseloggar.
- Ta bort Windows-tjänsten.
Dricks
Alla exempelkällkoden "Arbetare i .NET" finns i exempelwebbläsaren för nedladdning. Mer information finns i Bläddra bland kodexempel: Arbetare i .NET.
Viktigt!
När du installerar .NET SDK installeras även arbetsmallen Microsoft.NET.Sdk.Worker
och . När du har installerat .NET SDK kan du med andra ord skapa en ny arbetare med hjälp av kommandot dotnet new worker . Om du använder Visual Studio döljs mallen tills den valfria ASP.NET och webbutvecklingsarbetsbelastningen har installerats.
Förutsättningar
- .NET 8.0 SDK eller senare
- Ett Windows-operativsystem
- En .NET-integrerad utvecklingsmiljö (IDE)
- Använd gärna Visual Studio
Skapa ett nytt projekt
Om du vill skapa ett nytt Worker Service-projekt med Visual Studio väljer du Nytt>>filprojekt....I dialogrutan Skapa ett nytt projekt söker du efter "Arbetstjänst" och väljer Mall för Arbetstjänst. Om du hellre vill använda .NET CLI öppnar du din favoritterminal i en arbetskatalog. dotnet new
Kör kommandot och ersätt <Project.Name>
med önskat projektnamn.
dotnet new worker --name <Project.Name>
Mer information om kommandot .NET CLI new worker service project finns i dotnet new worker( dotnet new worker).
Dricks
Om du använder Visual Studio Code kan du köra .NET CLI-kommandon från den integrerade terminalen. Mer information finns i Visual Studio Code: Integrerad terminal.
Installera NuGet-paketet
Om du vill använda inbyggda Windows-tjänster från .NET-implementeringar IHostedService måste du installera Microsoft.Extensions.Hosting.WindowsServices
NuGet-paketet.
Om du vill installera detta från Visual Studio använder du dialogrutan Hantera NuGet-paket... . Sök efter "Microsoft.Extensions.Hosting.WindowsServices" och installera det. Om du hellre vill använda .NET CLI kör dotnet add package
du kommandot:
dotnet add package Microsoft.Extensions.Hosting.WindowsServices
Mer information om .NET CLI:s tilläggspaketkommando finns i dotnet add package ( dotnet add package).
När paketen har lagts till bör projektfilen nu innehålla följande paketreferenser:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="8.0.0" />
</ItemGroup>
Uppdatera projektfil
Det här arbetsprojektet använder sig av C#:s nullbara referenstyper. Om du vill aktivera dem för hela projektet uppdaterar du projektfilen i enlighet med detta:
<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>
Ändringarna i föregående projektfil lägger till <Nullable>enable<Nullable>
noden. Mer information finns i Ange den nullbara kontexten.
Skapa tjänsten
Lägg till en ny klass i projektet med namnet JokeService.cs och ersätt innehållet med följande C#-kod:
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);
Den föregående källkoden för skämttjänsten exponerar en enda funktion, GetJoke
metoden. Det här är en string
återkommande metod som representerar ett slumpmässigt programmeringsskämt. Fältet med klassomfattning _jokes
används för att lagra listan med skämt. Ett slumpmässigt skämt väljs från listan och returneras.
Skriv om Worker
klassen
Ersätt den befintliga Worker
från mallen med följande C#-kod och byt namn på filen till 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);
}
}
}
I föregående kod JokeService
matas in tillsammans med en ILogger
. Båda görs tillgängliga för klassen som private readonly
fält. ExecuteAsync
I metoden begär skämttjänsten ett skämt och skriver det till loggaren. I det här fallet implementeras loggaren av Windows-händelseloggen – Microsoft.Extensions.Logging.EventLog.EventLogLogger. Loggar skrivs till och är tillgängliga för visning i Loggboken.
Kommentar
Händelseloggens allvarlighetsgrad är Warningsom standard . Detta kan konfigureras, men i demonstrationssyfte WindowsBackgroundService
loggarna med LogWarning tilläggsmetoden. Om du vill rikta in dig specifikt på EventLog
nivån lägger du till en post i appettings.{ Environment}.json eller ange ett EventLogSettings.Filter värde.
{
"Logging": {
"LogLevel": {
"Default": "Warning"
},
"EventLog": {
"SourceName": "The Joke Service",
"LogName": "Application",
"LogLevel": {
"Microsoft": "Information",
"Microsoft.Hosting.Lifetime": "Information"
}
}
}
}
Mer information om hur du konfigurerar loggnivåer finns i Loggningsproviders i .NET: Konfigurera Windows EventLog.
Skriv om Program
klassen
Ersätt mallen Program.cs-filinnehållet med följande C#-kod:
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();
Tilläggsmetoden AddWindowsService
konfigurerar appen så att den fungerar som en Windows-tjänst. Tjänstnamnet är inställt på ".NET Joke Service"
. Den värdbaserade tjänsten är registrerad för beroendeinmatning.
Mer information om hur du registrerar tjänster finns i Beroendeinmatning i .NET.
Publicera appen
Om du vill skapa .NET Worker Service-appen som en Windows-tjänst rekommenderar vi att du publicerar appen som en körbar fil. Det är mindre felbenäget att ha en självständig körbar fil eftersom det inte finns några beroende filer som ligger runt filsystemet. Men du kan välja en annan publiceringsmodalitet, vilket är helt acceptabelt, så länge du skapar en *.exe-fil som kan riktas mot Windows Service Control Manager.
Viktigt!
En alternativ publiceringsmetod är att skapa *.dll (i stället för en *.exe) och när du installerar den publicerade appen med hjälp av Windows Service Control Manager delegerar du till .NET CLI och skickar DLL:en. Mer information finns i .NET CLI: dotnet-kommandot.
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>
De föregående markerade raderna i projektfilen definierar följande beteenden:
<OutputType>exe</OutputType>
: Skapar ett konsolprogram.<PublishSingleFile Condition="'$(Configuration)' == 'Release'">true</PublishSingleFile>
: Aktiverar publicering med en fil.<RuntimeIdentifier>win-x64</RuntimeIdentifier>
: Anger RIDwin-x64
för .<PlatformTarget>x64</PlatformTarget>
: Ange målplattformens PROCESSOR på 64-bitars.
Om du vill publicera appen från Visual Studio kan du skapa en publiceringsprofil som är sparad. Publiceringsprofilen är XML-baserad och har filnamnstillägget .pubxml . Visual Studio använder den här profilen för att publicera appen implicit, medan du måste uttryckligen ange publiceringsprofilen för att den ska användas om du använder .NET CLI.
Högerklicka på projektet i Solution Explorer och välj Publicera.... Välj sedan Lägg till en publiceringsprofil för att skapa en profil. I dialogrutan Publicera väljer du Mapp som mål.
Lämna standardplatsen och välj sedan Slutför. När profilen har skapats väljer du Visa alla inställningar och verifierar dina profilinställningar.
Kontrollera att följande inställningar har angetts:
- Distributionsläge: Fristående
- Skapa en enskild fil: markerad
- Aktivera ReadyToRun-kompilering: markerat
- Trimma oanvända sammansättningar (i förhandsversion): avmarkerat
Välj slutligen Publicera. Appen kompileras och den resulterande .exe-filen publiceras till utdatakatalogen /publish .
Du kan också använda .NET CLI för att publicera appen:
dotnet publish --output "C:\custom\publish\directory"
Mer information finns i dotnet publish
.
Viktigt!
Om du försöker felsöka appen med inställningen med <PublishSingleFile>true</PublishSingleFile>
.NET 6 kan du inte felsöka appen. Mer information finns i Det går inte att ansluta till CoreCLR när du felsöker en .NET 6-app för PublishSingleFile.
Skapa Windows-tjänsten
Om du inte känner till att använda PowerShell och hellre skapar ett installationsprogram för din tjänst kan du läsa Skapa ett Installationsprogram för Windows-tjänsten. Om du vill skapa Windows-tjänsten använder du annars det interna kommandot för att skapa Windows Service Control Manager (sc.exe). Kör PowerShell som administratör.
sc.exe create ".NET Joke Service" binpath="C:\Path\To\App.WindowsService.exe"
Dricks
Om du behöver ändra innehållsroten i värdkonfigurationen kan du skicka den som ett kommandoradsargument när du anger binpath
:
sc.exe create "Svc Name" binpath="C:\Path\To\App.exe --contentRoot C:\Other\Path"
Ett utdatameddelande visas:
[SC] CreateService SUCCESS
Mer information finns i skapa sc.exe.
Konfigurera Windows-tjänsten
När tjänsten har skapats kan du konfigurera den. Om du är okej med standardinställningarna för tjänsten går du vidare till avsnittet Verifiera tjänstfunktioner .
Windows Services tillhandahåller återställningskonfigurationsalternativ. Du kan köra frågor mot den aktuella konfigurationen sc.exe qfailure "<Service Name>"
med hjälp av kommandot (var <Service Name>
är dina tjänsters namn) för att läsa de aktuella återställningskonfigurationsvärdena:
sc qfailure ".NET Joke Service"
[SC] QueryServiceConfig2 SUCCESS
SERVICE_NAME: .NET Joke Service
RESET_PERIOD (in seconds) : 0
REBOOT_MESSAGE :
COMMAND_LINE :
Kommandot matar ut återställningskonfigurationen, som är standardvärdena, eftersom de ännu inte har konfigurerats.
Om du vill konfigurera återställning använder sc.exe failure "<Service Name>"
du var <Service Name>
är namnet på din tjänst:
sc.exe failure ".NET Joke Service" reset=0 actions=restart/60000/restart/60000/run/1000
[SC] ChangeServiceConfig2 SUCCESS
Dricks
För att konfigurera återställningsalternativen måste terminalsessionen köras som administratör.
När den har konfigurerats kan du köra frågor mot värdena igen med kommandot 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.
Du ser de konfigurerade omstartsvärdena.
Alternativ för tjänståterställning och .NET-instanser BackgroundService
Med .NET 6 har nya beteenden för hantering av undantagshantering lagts till i .NET. Uppräkningen BackgroundServiceExceptionBehavior lades till i Microsoft.Extensions.Hosting
namnområdet och används för att ange beteendet för tjänsten när ett undantag utlöses. I följande tabell visas tillgängliga alternativ:
Alternativ | Description |
---|---|
Ignore | Ignorera undantag som genereras i BackgroundService . |
StopHost | IHost Stoppas när ett ohanterat undantag utlöses. |
Standardbeteendet före .NET 6 är Ignore
, vilket resulterade i zombieprocesser (en körningsprocess som inte gjorde något). Med .NET 6 är StopHost
standardbeteendet , vilket resulterar i att värden stoppas när ett undantag utlöses. Men det stoppas rent, vilket innebär att Windows Service-hanteringssystemet inte startar om tjänsten. För att tjänsten ska kunna startas om på rätt sätt kan du anropa Environment.Exit med en slutkod som inte är noll. Överväg följande markerade catch
block:
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);
}
}
}
Verifiera tjänstfunktioner
Om du vill se appen som skapats som en Windows-tjänst öppnar du Tjänster. Välj Windows-tangenten (eller Ctrl + Esc) och sök från "Tjänster". Från appen Tjänster bör du kunna hitta din tjänst med dess namn.
Viktigt!
Som standard kan vanliga (icke-administratörs) användare inte hantera Windows-tjänster. För att verifiera att den här appen fungerar som förväntat måste du använda ett administratörskonto.
För att kontrollera att tjänsten fungerar som förväntat måste du:
- Starta tjänsten
- Visa loggarna
- Stoppa tjänsten
Viktigt!
Om du vill felsöka programmet kontrollerar du att du inte försöker felsöka den körbara fil som körs aktivt i Windows Services-processen.
Starta Windows-tjänsten
Använd kommandot för att starta Windows-tjänsten sc.exe start
:
sc.exe start ".NET Joke Service"
Du ser utdata som liknar följande:
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
Tjänststatusenövergår från START_PENDING
till Körs.
Visa loggar
Om du vill visa loggar öppnar du Loggboken. Välj Windows-tangenten (eller Ctrl + Esc) och sök "Event Viewer"
efter . Välj noden Loggboken (lokal)>Windows-loggar>program. Du bör se en varningsnivåpost med en källa som matchar appnamnområdet. Dubbelklicka på posten eller högerklicka och välj Händelseegenskaper för att visa informationen.
När du har sett loggar i händelseloggen bör du stoppa tjänsten. Den är utformad för att logga ett slumpmässigt skämt en gång per minut. Detta är avsiktligt beteende men är inte praktiskt för produktionstjänster.
Stoppa Windows-tjänsten
Om du vill stoppa Windows-tjänsten använder du sc.exe stop
kommandot:
sc.exe stop ".NET Joke Service"
Du ser utdata som liknar följande:
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
Tjänststatusenövergår från STOP_PENDING
till Stoppad.
Ta bort Windows-tjänsten
Om du vill ta bort Windows-tjänsten använder du det interna borttagningskommandot för Windows Service Control Manager (sc.exe). Kör PowerShell som administratör.
Viktigt!
Om tjänsten inte är i tillståndet Stoppad tas den inte bort omedelbart. Kontrollera att tjänsten har stoppats innan du utfärdar borttagningskommandot.
sc.exe delete ".NET Joke Service"
Ett utdatameddelande visas:
[SC] DeleteService SUCCESS
Mer information finns i sc.exe delete.
Se även
- Skapa ett Windows Service-installationsprogram
- Worker Services i .NET
- Skapa en kötjänst
- Använda begränsade tjänster inom en
BackgroundService
IHostedService
Implementera gränssnittet