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

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-x64fö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.

The Visual Studio Publish dialog

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.

The Visual Studio Profile settings

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.

The Windows Service recovery configuration properties dialog.

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.

The Windows Service recovery configuration properties dialog with restart enabled.

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 StopHoststandardbeteendet , 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.

The Services user interface.

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.

Unable to start program.

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.

The Event Properties dialog, with details logged from the service

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

Nästa