Dela via


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 i ett enda paket.
  • Skapa en Windows-tjänst.
  • Skapa BackgroundService-appen som en Windows-tjänst.
  • Starta och stoppa Windows-tjänsten.
  • Visa händelseloggar.
  • Ta bort Windows-tjänsten.

Tips

Alla exempelkällkoden "Arbetare i .NET" finns i Samples Browser för nedladdning. Mer information finns i Bläddra bland kodexempel: Arbetare i .NET.

Viktig

När du installerar .NET SDK installeras även Microsoft.NET.Sdk.Worker och arbetsmallen. 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

Skapa ett nytt projekt

Om du vill skapa ett nytt Worker Service-projekt med Visual Studio väljer du File>New>Project.... I dialogrutan Skapa ett nytt projekt sök efter "Arbetstjänst" och välj Mall för arbetstjänst. Om du hellre vill använda .NET CLI öppnar du din favoritterminal i en arbetskatalog. Kör kommandot dotnet new och ersätt <Project.Name> med önskat projektnamn.

dotnet new worker --name <Project.Name>

För mer information om kommandot .NET CLI för att skapa ett nytt worker-tjänsteprojekt, se dotnet new worker.

Tips

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: Integrated Terminal.

Installera NuGet-paketet

Om du vill använda inbyggda Windows-tjänster från .NET IHostedService-implementeringar 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 du kommandot dotnet add package:

dotnet add package Microsoft.Extensions.Hosting.WindowsServices

Mer information om kommandot .NET CLI add package finns i 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="9.0.3" />
  <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.3" />
</ItemGroup>

Uppdatera projektfil

Det här arbetsprojektet använder C#:s null-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="9.0.3" />
    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="9.0.3" />
  </ItemGroup>
</Project>

Ändringarna i föregående projektfil lägger till noden <Nullable>enable<Nullable>. Mer information finns under Inställning av nullbart sammanhang.

Skapa tjänsten

Lägg till en ny klass i projektet med namnet JokeService.csoch 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);

Källkoden för den föregående skämttjänsten exponerar en enda funktion, GetJoke-metoden. Det här är en string-metod som returnerar ett slumpmässigt programmeringsskämt. Det klassomfångade fältet _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 klassen Worker

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 matas JokeService in tillsammans med en ILogger. Båda görs tillgängliga för klassen som fältvariabler. I ExecuteAsync-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.EventLogLoggerProvider. Loggar skrivs till och är tillgängliga för visning i Loggboken.

Anteckning

Som standardinställning är allvarlighetsgraden för händelseloggen Warning. Detta kan konfigureras, men i demonstrationssyfte loggar WindowsBackgroundService med LogWarning-tilläggsmetoden. För att specifikt rikta in dig på EventLog-nivån, lägg till en post i appsettings .{Environment}.json, eller ange ett värde för EventLogSettings.Filter.

{
  "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 klassen Program

Ersätt mallen Program.cs filinnehåll 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();

Metoden AddWindowsService-tillägg 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 beroendeinjektion.

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.

Viktig

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 command.

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>

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 RID- för win-x64.
  • <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, men om du använder .NET CLI måste du uttryckligen ange publiceringsprofilen för att den ska användas.

Högerklicka på projektet i Solution Exploreroch 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 ditt Mål.

Publicera-dialogrutan i Visual Studio

Lämna standardinställningen Platsoch välj sedan Slutför. När profilen har skapats väljer du Visa alla inställningaroch kontrollerar Profilinställningar.

Visual Studio-profilinställningarna

Kontrollera att följande inställningar har angetts:

  • Distributionsläge: Fristående
  • Skapa en enda fil: kontrollerad
  • Aktivera ReadyToRun-kompilering: markerat
  • Trimma oanvända sammansättningar (i förhandsversion): avmarkerad

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.

Viktig

Om du försöker felsöka appen med inställningen <PublishSingleFile>true</PublishSingleFile> med .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 "PublishSingleFile" .NET 6-app.

Skapa Windows-tjänsten

Om du inte känner till att använda PowerShell och hellre skapar ett installationsprogram för tjänsten kan du läsa Skapa ett Windows Service-installationsprogram. Om du vill skapa Windows-tjänsten använder du annars det inbyggda windows Service Control Manager-kommandot (sc.exe) create. Kör PowerShell som administratör.

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

Tips

Om du behöver ändra innehållsroten för värdkonfigurationkan 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 sc.exe skapa.

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 fråga efter den nuvarande konfigurationen med hjälp av kommandot sc.exe qfailure "<Service Name>" (där <Service Name> är namnet på dina tjänster) för att läsa de nuvarande återställningsvärdena för konfigurationen.

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.

Windows-tjänstens återställningskonfigurationsdialog.

Om du vill konfigurera återställning använder du sc.exe failure "<Service Name>" där <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

Tips

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 hjälp av 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.

Dialogrutan för konfigurationsegenskaper för Windows-tjänsts återställning med omstart aktiverad.

Alternativ för tjänståterställning och .NET BackgroundService-instanser

Med .NET 6 har nya värdbeteenden för undantagshantering lagts till i .NET. BackgroundServiceExceptionBehavior-uppräkningen 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 Beskrivning
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 process som körde men inte gjorde något). Med .NET 6 är standardbeteendet StopHost, vilket resulterar i att värdtjänsten stoppas när ett undantag kastas. Men det avslutas ordentligt, vilket innebär att Windows tjänstehanteringssystem 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 det markerade blocket 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);
        }
    }
}

Verifiera tjänstfunktioner

Om du vill se appen som skapats som en Windows-tjänst öppnar du Services. Välj Windows-nyckeln (eller Ctrl + Esc) och sök efter "Tjänster". Från appen Services bör du kunna hitta tjänsten med dess namn.

Viktig

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.

Användargränssnittet för tjänster.

För att kontrollera att tjänsten fungerar som förväntat måste du:

  • Starta tjänsten
  • Visa loggarna
  • Stoppa tjänsten

Viktig

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.

Det går inte att starta programmet.

Starta Windows-tjänsten

Starta Windows-tjänsten genom att använda kommandot 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änsten Status övergår från START_PENDING till Running.

Visa loggar

Om du vill visa loggar öppnar du Händelsevisaren. Välj Windows-nyckeln (eller Ctrl + Esc) och sök efter "Event Viewer". Välj noden Händelsevyn (lokal)>Windows-loggar>Program. Du bör se en varning nivåpost med en Source som matchar appnamnområdet. Dubbelklicka på posten eller högerklicka och välj Händelseegenskaper för att visa informationen.

dialogrutan Händelseegenskaper, med detaljer loggade från tjänsten

När du har sett loggar i händelseloggenbö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 kommandot sc.exe stop:

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änsten Status ska övergå 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.

Viktig

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 ta bort.

Se även

Nästa