Delen via


Werkerdiensten in .NET

Er zijn talloze redenen voor het maken van langlopende services, zoals:

  • Cpu-intensieve gegevens verwerken.
  • Werkitems op de achtergrond in de wachtrij plaatsen.
  • Een tijdgebaseerde bewerking uitvoeren op basis van een schema.

Verwerking van achtergrondservices omvat meestal geen gebruikersinterface (UI), maar UIs kunnen er omheen worden gebouwd. In de vroege dagen met .NET Framework konden Windows-ontwikkelaars Windows Services maken voor deze doeleinden. Met .NET kunt u nu de BackgroundServicegebruiken, een implementatie van IHostedService, of zelf implementeren.

Met .NET bent u niet langer beperkt tot Windows. U kunt platformoverschrijdende achtergrondservices ontwikkelen. Gehoste services zijn gereed voor logboekregistratie, configuratie en afhankelijkheidsinjectie (DI). Ze maken deel uit van de uitbreidingssuite met bibliotheken, wat betekent dat ze fundamenteel zijn voor alle .NET-workloads die werken met de algemene host.

Belangrijk

Als u de .NET SDK installeert, worden ook de Microsoft.NET.Sdk.Worker en de werkersjabloon geïnstalleerd. Met andere woorden, nadat u de .NET SDK hebt geïnstalleerd, kunt u een nieuwe werkrol maken met behulp van de opdracht dotnet new worker. Als u Visual Studio gebruikt, wordt de sjabloon verborgen totdat de optionele workload voor ASP.NET en webontwikkeling is geïnstalleerd.

Terminologie

Veel termen worden ten onrechte als synoniemen gebruikt. In deze sectie worden enkele van deze termen gedefinieerd om hun intentie in dit artikel duidelijker te maken.

  • Background Service: het type BackgroundService.
  • gehoste dienst: implementaties van IHostedServiceof IHostedService zelf.
  • langlopende service: elke service die continu wordt uitgevoerd.
  • Windows-service: de Windows-service-infrastructuur, oorspronkelijk gericht op .NET Framework, maar nu toegankelijk via .NET.
  • Worker Service: de Worker Service sjabloon.

Worker Service-sjabloon

De Worker Service-sjabloon is beschikbaar in de .NET CLI en Visual Studio. Zie .NET CLI, dotnet new worker - sjabloonvoor meer informatie. De sjabloon bestaat uit een Program- en Worker-klasse.

using App.WorkerService;

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

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

De voorgaande Program klasse:

  • Hiermee maak je een HostApplicationBuilder.
  • Roept AddHostedService aan om de Worker te registreren als een gehoste service.
  • Bouwt een IHost vanuit de bouwer.
  • Roept Run aan op het host-exemplaar, waarmee de app wordt uitgevoerd.

Standaardinstellingen voor sjablonen

De sjabloon schakelt standaard de garbage collection (GC) van de server niet in, omdat er talloze factoren zijn die een rol spelen bij het bepalen van de noodzaak ervan. Alle scenario's waarvoor langlopende services zijn vereist, moeten rekening houden met de gevolgen voor de prestaties van deze standaard. Als u server GC wilt inschakelen, voegt u het knooppunt ServerGarbageCollection toe aan het projectbestand:

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

compromissen en overwegingen

Ingeschakeld Uitgeschakeld
Efficiënt geheugenbeheer: maakt ongebruikt geheugen automatisch vrij om geheugenlekken te voorkomen en resourcegebruik te optimaliseren. ** Verbeterde realtime prestaties: vermijdt mogelijke pauzes of onderbrekingen die worden veroorzaakt door garbage-collection in latentiegevoelige toepassingen.
Langetermijnstabiliteit: helpt stabiele prestaties in langlopende services te behouden door het geheugen gedurende langere perioden te beheren. Resource-efficiëntie: kan CPU- en geheugenresources besparen in omgevingen met beperkte resources.
Verminderd onderhoud: minimaliseert de noodzaak van handmatig geheugenbeheer, waardoor onderhoud wordt vereenvoudigd. Handmatig geheugenbeheer: biedt nauwkeurige controle over het geheugen voor gespecialiseerde toepassingen.
Voorspelbaar gedrag: draagt bij aan consistent en voorspelbaar toepassingsgedrag. Geschikt voor processen van korte duur: vermindert de belasting van afvalverzameling voor kortstondige of tijdelijke processen.

Zie Server GCvoor meer informatie over prestatieoverwegingen. Zie Server GC-configuratievoorbeeldenvoor meer informatie over het configureren van server-GC.

Werkersklasse

Wat de Workerbetreft, biedt de sjabloon een eenvoudige implementatie.

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);
        }
    }
}

De voorgaande Worker klasse is een subklasse van BackgroundService, waarmee IHostedServicewordt geïmplementeerd. De BackgroundService is een abstract class en vereist dat de subklasse BackgroundService.ExecuteAsync(CancellationToken)implementeert. In de sjabloon-implementatie wordt de ExecuteAsync eenmaal per seconde herhaald, waarbij de huidige datum en tijd worden geregistreerd totdat het proces wordt gesignaleerd om te annuleren.

Het projectbestand

De werksjabloon is afhankelijk van het volgende projectbestand Sdk:

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

Zie SDK's voor .NET-projectenvoor meer informatie.

NuGet-pakket

Een app op basis van de Worker-sjabloon maakt gebruik van de Microsoft.NET.Sdk.Worker SDK en heeft een expliciete pakketverwijzing naar het pakket Microsoft.Extensions.Hosting.

Containers en cloud-aanpassingsvermogen

Bij de meeste moderne .NET-workloads zijn containers een haalbare optie. Wanneer u een langlopende service maakt vanuit de Worker-sjabloon in Visual Studio, kunt u zich aanmelden voor Docker-ondersteuning. Hiermee maakt u een Dockerfile waarmee uw .NET-app in een container wordt geplaatst. Een Dockerfile- is een verzameling instructies om een image te bouwen. Voor .NET-applicaties bevindt de Dockerfile zich meestal in de hoofdmap naast een oplossingsbestand.

# 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"]

De voorgaande Dockerfile stappen zijn:

  • De basisafbeelding van mcr.microsoft.com/dotnet/runtime:8.0 instellen als de alias base.
  • De werkmap wijzigen naar /app.
  • De build-alias instellen vanuit de mcr.microsoft.com/dotnet/sdk:8.0-afbeelding.
  • De werkmap wijzigen naar /src.
  • De inhoud kopiëren en de .NET-app publiceren:
    • De app wordt gepubliceerd met behulp van de opdracht dotnet publish.
  • Herstructureren van de .NET SDK-image van mcr.microsoft.com/dotnet/runtime:8.0 (de base alias).
  • Het kopiëren van de gepubliceerde build-uitvoer uit de /publish.
  • Het toegangspunt definiëren, dat aan dotnet App.BackgroundService.dlldelegeert.

Aanbeveling

De MCR in mcr.microsoft.com staat voor 'Microsoft Container Registry' en is de gesyndiceerde containercatalogus van Microsoft van de officiële Docker-hub. Het Microsoft syndicates containercatalogus artikel bevat aanvullende informatie.

Wanneer u Docker als implementatiestrategie voor uw .NET Worker Service kiest, zijn er enkele overwegingen die u in het projectbestand moet maken:

<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.7" />
    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
  </ItemGroup>
</Project>

In het voorgaande projectbestand geeft het <DockerDefaultTargetOS> element Linux op als doel. Gebruik in plaats daarvan Windows om Windows-containers te targeten. Het Microsoft.VisualStudio.Azure.Containers.Tools.Targets NuGet-pakket wordt automatisch toegevoegd als pakketreferentie wanneer Docker-ondersteuning is geselecteerd in de sjabloon.

Zie Zelfstudie: Een .NET-app in een container opslaanvoor meer informatie over Docker met .NET. Zie zelfstudie: Een werkservice implementeren in Azurevoor meer informatie over het implementeren in Azure.

Belangrijk

Als u User Secrets wilt gebruiken met de worker-sjabloon, moet u expliciet verwijzen naar het Microsoft.Extensions.Configuration.UserSecrets NuGet-pakket.

Uitbreidbaarheid van gehoste services

De IHostedService-interface definieert twee methoden:

Deze twee methoden fungeren als levenscyclus methoden: ze worden respectievelijk aangeroepen tijdens het starten en stoppen van de host.

Notitie

Wanneer u de methoden StartAsync of StopAsync overschrijft, moet u eerst de klassemethode await aanroepen en vervolgens base uitvoeren om ervoor te zorgen dat de service correct wordt gestart en/of afgesloten.

Belangrijk

De interface fungeert als een algemene parameterbeperking voor de extensiemethode AddHostedService<THostedService>(IServiceCollection), wat betekent dat alleen implementaties zijn toegestaan. U kunt de meegeleverde BackgroundService met een subklasse gebruiken of uw eigen subklasse implementeren.

Voltooiing van het signaal

In de meest voorkomende scenario's hoeft u niet expliciet de voltooiing van een gehoste service aan te geven. Wanneer de host de services start, zijn ze ontworpen om te worden uitgevoerd totdat de host is gestopt. In sommige scenario's moet u echter mogelijk aangeven dat de volledige hosttoepassing is voltooid wanneer de service is voltooid. Houd rekening met de volgende Worker klasse om de voltooiing aan te geven:

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();
    }
}

In de voorgaande code loopt de methode BackgroundService.ExecuteAsync(CancellationToken) niet, en wordt IHostApplicationLifetime.StopApplication()aangeroepen wanneer deze is voltooid.

Belangrijk

Dit geeft aan dat de host moet worden gestopt, en zonder deze aanroep naar StopApplication zal de host voor onbepaalde tijd blijven draaien. Als u van plan bent om een kortdurende gehoste service uit te voeren (één scenario uitvoeren) en u de worker-sjabloon wilt gebruiken, moet u aanroepen StopApplication om de host te signaleren om te stoppen.

Zie voor meer informatie:

Alternatieve benadering

Gebruik voor een kortdurende app die afhankelijkheidsinjectie, logboekregistratie en configuratie nodig heeft, de .NET Generic Host in plaats van de Worker-sjabloon. Hiermee kunt u deze functies zonder de Worker klasse gebruiken. Een eenvoudig voorbeeld van een kortdurende app met behulp van de algemene host kan een projectbestand als volgt definiëren:

<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="10.0.1" />
  </ItemGroup>
</Project>

Dit is Program klasse en deze kan er als volgt uitzien:

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
}

Met de voorgaande code wordt een JobRunner service gemaakt. Dit is een aangepaste klasse die de logica bevat voor de taak die moet worden uitgevoerd. De RunAsync methode wordt aangeroepen op de JobRunner, en als deze succesvol is voltooid, retourneert de app 0. Als er een niet-verwerkte uitzondering optreedt, wordt de fout in een logboek opgeslagen en geretourneerd 1.

In dit eenvoudige scenario kan de JobRunner klasse er als volgt uitzien:

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.");
    }
}

U zou uiteraard echte logica aan de RunAsync methode moeten toevoegen, maar in dit voorbeeld ziet u hoe u de algemene host gebruikt voor een kortdurende app zonder dat er een Worker klasse nodig is en zonder dat u expliciet de voltooiing van de host hoeft aan te geven.

Zie ook