Dela via


Arbetstjänster i .NET

Det finns många orsaker till att skapa långvariga tjänster, till exempel:

  • Bearbetning av processorintensiva data.
  • Placera arbetsuppgifter i kö för bakgrunden.
  • Utföra en tidsbaserad åtgärd enligt ett schema.

Bearbetning av bakgrundstjänst omfattar vanligtvis inte något användargränssnitt, men UIs kan byggas runt dem. I början med .NET Framework kunde Windows-utvecklare skapa Windows-tjänster för dessa ändamål. Nu med .NET kan du använda BackgroundService, som är en implementering av IHostedServiceeller implementera din egen.

Med .NET är du inte längre begränsad till Windows. Du kan utveckla plattformsoberoende bakgrundstjänster. Värdbaserade tjänster är redo för loggning, konfiguration och beroendeinjektion (DI). De ingår i tilläggspaketet med bibliotek, vilket innebär att de är grundläggande för alla .NET-arbetsbelastningar som fungerar med generiska värd.

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.

Terminologi

Många termer används av misstag synonymt. Det här avsnittet definierar några av dessa termer för att göra deras avsikt i den här artikeln tydligare.

  • Background Service: BackgroundService-typen.
  • värdbaserad tjänst: Implementeringar av IHostedServiceeller själva IHostedService.
  • Långvarig tjänst: Alla tjänster som körs kontinuerligt.
  • Windows Service: Windows Service infrastruktur, ursprungligen .NET Framework-centrerad men nu tillgänglig via .NET.
  • Worker Service: Worker Service mallen.

Arbetarstjänstmall

Arbetstjänstmallen är tillgänglig i .NET CLI och Visual Studio. Mer information finns i .NET CLI, dotnet new worker – template. Mallen består av en Program- och Worker-klass.

using App.WorkerService;

HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();

IHost host = builder.Build();
host.Run();

Föregående Program-klass:

Standardinställningar för mallar

Worker-mallen aktiverar inte server skräpinsamling (GC) som standard, eftersom det finns många faktorer som spelar en roll för att fastställa dess nödvändighet. Alla scenarier som kräver långvariga tjänster bör ta hänsyn till prestandakonsekvenserna av den här standardinställningen. Om du vill aktivera server-GC lägger du till noden ServerGarbageCollection i projektfilen:

<PropertyGroup>
    <ServerGarbageCollection>true</ServerGarbageCollection>
</PropertyGroup>

kompromisser och överväganden

Aktiverat Handikappad
Effektiv minneshantering: Frigör automatiskt oanvänt minne för att förhindra minnesläckor och optimera resursanvändningen. Förbättrad realtidsprestanda: Undviker potentiella pauser eller avbrott som orsakas av skräpinsamling i svarstidskänsliga program.
Långsiktig stabilitet: Hjälper till att upprätthålla stabila prestanda i långvariga tjänster genom att hantera minne under längre perioder. Resurseffektivitet: Kan spara processor- och minnesresurser i resursbegränsade miljöer.
Minskat underhåll: Minimerar behovet av manuell minneshantering, vilket förenklar underhållet. Manuell minneskontroll: Ger detaljerad kontroll över minnet för specialiserade program.
Förutsägbart beteende: Bidrar till konsekvent och förutsägbart programbeteende. Lämplig för kortvariga processer: Minimerar kostnaderna för skräpinsamling för kortvariga eller tillfälliga processer.

Mer information om prestandaöverväganden finns i Server GC. Mer information om hur du konfigurerar server GC finns i Server GC-konfigurationsexempel.

Arbetarklass

När det gäller Workertillhandahåller mallen en enkel implementering.

namespace App.WorkerService;

public sealed class Worker(ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1_000, stoppingToken);
        }
    }
}

Föregående Worker-klass är en underklass av BackgroundService, som implementerar IHostedService. BackgroundService är en abstract class och kräver att underklassen implementerar BackgroundService.ExecuteAsync(CancellationToken). I mallimplementeringen loopar ExecuteAsync en gång per sekund och loggar aktuellt datum och tid tills processen signaleras avbrytas.

Projektfilen

Mallen Worker förlitar sig på följande projektfil Sdk:

<Project Sdk="Microsoft.NET.Sdk.Worker">

Mer information finns i .NET-projekt-SDK:er.

NuGet-paket

En app som baseras på worker-mallen använder Microsoft.NET.Sdk.Worker SDK och har en explicit paketreferens till Microsoft.Extensions.Hosting-paketet.

Containrar och molnanpassning

Med de flesta moderna .NET-arbetsbelastningar är containrar ett genomförbart alternativ. När du skapar en långvarig tjänst från Arbetsmallen i Visual Studio kan du välja att Docker-stöd. När du gör det skapas en Dockerfile- som containeriserar .NET-appen. En Dockerfile är en uppsättning instruktioner för att skapa en avbildning. För .NET-appar finns vanligtvis Dockerfile i roten i katalogen bredvid en lösningsfil.

# See https://aka.ms/containerfastmode to understand how Visual Studio uses this
# Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/runtime:8.0@sha256:e6b552fd7a0302e4db30661b16537f7efcdc0b67790a47dbf67a5e798582d3a5 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0@sha256:35792ea4ad1db051981f62b313f1be3b46b1f45cadbaa3c288cd0d3056eefb83 AS build
WORKDIR /src
COPY ["background-service/App.WorkerService.csproj", "background-service/"]
RUN dotnet restore "background-service/App.WorkerService.csproj"
COPY . .
WORKDIR "/src/background-service"
RUN dotnet build "App.WorkerService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "App.WorkerService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "App.WorkerService.dll"]

I de föregående Dockerfile stegen ingår:

  • Ange basavbildningen från mcr.microsoft.com/dotnet/runtime:8.0 som aliaset base.
  • Ändra arbetskatalogen till /app.
  • Ange build alias från mcr.microsoft.com/dotnet/sdk:8.0-avbildningen.
  • Ändra arbetskatalogen till /src.
  • Kopiera innehållet och publicera .NET-appen:
  • Omlagra .NET SDK-bilden från mcr.microsoft.com/dotnet/runtime:8.0 (aliaset base).
  • Kopiera publicerade build-utdata från /publish.
  • Definiering av startpunkten, som delegerar till dotnet App.BackgroundService.dll.

Tips

MCR i mcr.microsoft.com står för "Microsoft Container Registry" och är Microsofts syndikerade containerkatalog från den officiella Docker-hubben. Artikel om Microsoft-syndikerings containerkatalog innehåller ytterligare information.

När du riktar in dig på Docker som distributionsstrategi för .NET Worker Service finns det några saker att tänka på i projektfilen:

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>App.WorkerService</RootNamespace>
    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
  </ItemGroup>
</Project>

I föregående projektfil anger elementet <DockerDefaultTargetOS>Linux som mål. Använd Windows i stället för att rikta in dig på Windows-containrar. Microsoft.VisualStudio.Azure.Containers.Tools.Targets NuGet-paketet läggs automatiskt till som en paketreferens när Docker-stöd väljs från mallen.

Mer information om Docker med .NET finns i Tutorial: Containerize a .NET app. Mer information om hur du distribuerar till Azure finns i Självstudie: Distribuera en arbetstjänst till Azure.

Viktig

Om du vill använda användarhemligheter med Worker-mallen måste du uttryckligen ange Microsoft.Extensions.Configuration.UserSecrets NuGet-paketet.

Utökningsbarhet för värdbaserad tjänst

Gränssnittet IHostedService definierar två metoder:

Dessa två metoder fungerar som livscykel metoder – de anropas under värdstarts- respektive stopphändelser.

Obs

När du åsidosättar antingen StartAsync eller StopAsync metoder måste du anropa och awaitbase-klassmetoden för att säkerställa att tjänsten startas och/eller stängs av korrekt.

Viktig

Gränssnittet fungerar som en parameterbegränsning av generisk typ för AddHostedService<THostedService>(IServiceCollection)-tilläggsmetoden, vilket innebär att endast implementeringar tillåts. Du kan använda den angivna BackgroundService med en underklass eller implementera din egen helt.

Signalera färdigställande

I de vanligaste scenarierna behöver du inte uttryckligen signalera slutförandet av en värdbaserad tjänst. När värden startar tjänsterna är de avsedda att köras tills värden stoppas. I vissa scenarier kan du dock behöva signalera slutförandet av hela värdprogrammet när tjänsten är klar. Tänk på följande Worker-klass för att signalera slutförandet:

namespace App.SignalCompletionService;

public sealed class Worker(
    IHostApplicationLifetime hostApplicationLifetime,
    ILogger<Worker> logger) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // TODO: implement single execution logic here.
        logger.LogInformation(
            "Worker running at: {Time}", DateTimeOffset.Now);

        await Task.Delay(1_000, stoppingToken);

        // When completed, the entire app host will stop.
        hostApplicationLifetime.StopApplication();
    }
}

I föregående kod loopar inte metoden BackgroundService.ExecuteAsync(CancellationToken) och när den är klar anropas IHostApplicationLifetime.StopApplication().

Viktig

Detta kommer att signalera till värden att den ska stoppa, och utan det här anropet till StopApplication kommer värden att fortsätta köras på obestämd tid. Om du tänker köra en kortvarig värdbaserad tjänst (kör en gång i det aktuella scenariot) och du vill använda Worker-mallen måste du anropa StopApplication för att ge värden en signal om att stoppa.

Mer information finns i:

Alternativt tillvägagångssätt

För en kortlivad app som behöver beroendeinjektion, loggning och konfiguration, använder du den generiska .NET-värden i stället för Worker-mallen. På så sätt kan du använda dessa funktioner utan Worker klassen. Ett enkelt exempel på en kortlivad app som använder den generiska värden kan definiera en projektfil enligt följande:

<Project Sdk="Microsoft.NET.Sdk.Worker">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>true</ImplicitUsings>
    <RootNamespace>ShortLived.App</RootNamespace>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.6" />
  </ItemGroup>
</Project>

Dess Program-klass kan se ut ungefär så här:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<JobRunner>();

using var host = builder.Build();

try
{
    var runner = host.Services.GetRequiredService<JobRunner>();

    await runner.RunAsync();

    return 0; // success
}
catch (Exception ex)
{
    var logger = host.Services.GetRequiredService<ILogger<Program>>();
    
    logger.LogError(ex, "Unhandled exception occurred during job execution.");

    return 1; // failure
}

Föregående kod skapar en JobRunner tjänst, som är en anpassad klass som innehåller logiken för jobbet som ska köras. Metoden RunAsync anropas på JobRunner, och om den slutförs framgångsrikt returnerar appen 0. Om ett ohanterat undantag inträffar loggar det felet och returnerar 1.

I det här enkla scenariot JobRunner kan klassen se ut så här:

using Microsoft.Extensions.Logging;

internal sealed class JobRunner(ILogger<JobRunner> logger)
{
    public async Task RunAsync()
    {
        logger.LogInformation("Starting job...");

        // Simulate work
        await Task.Delay(1000);

        // Simulate failure
        // throw new InvalidOperationException("Something went wrong!");

        logger.LogInformation("Job completed successfully.");
    }
}

Du skulle naturligtvis behöva lägga till verklig logik i RunAsync metoden, men det här exemplet visar hur du använder den generiska värd för en kortlivad app utan att behöva en Worker klass, och utan att uttryckligen behöva signalera slutförandet av värdens.

Se även