Sdílet prostřednictvím


Pracovní služby v .NET

Existuje mnoho důvodů pro vytváření dlouhotrvajících služeb, například:

  • Zpracování dat náročných na procesor
  • Řazení pracovních položek do fronty na pozadí
  • Provádění operace založené na čase podle plánu.

Zpracování služby na pozadí obvykle nezahrnuje uživatelské rozhraní, ale uživatelská rozhraní je možné kolem nich sestavit. V prvních dnech s rozhraním .NET Framework mohou vývojáři windows pro tyto účely vytvářet služby Systému Windows. Nyní s .NET můžete použít BackgroundService, což je implementace IHostedService, nebo implementovat vlastní.

S .NET už nejste omezeni na Windows. Můžete vyvíjet multiplatformní služby na pozadí. Hostované služby jsou připravené pro protokolování, konfiguraci a injektáž závislostí (DI). Jsou součástí sady rozšíření knihoven, což znamená, že jsou zásadní pro všechny úlohy .NET, které pracují s obecným hostitelem.

Důležitý

Instalace sady .NET SDK také nainstaluje Microsoft.NET.Sdk.Worker a pracovní šablonu. Jinými slovy, po instalaci sady .NET SDK můžete vytvořit nový pracovní proces pomocí příkazu dotnet new worker. Pokud používáte Visual Studio, šablona se skryje, dokud se nenainstaluje volitelná ASP.NET a úloha vývoje webu.

Terminologie

Mnoho termínů se omylem používá jako synonymum. Tato část definuje některé z těchto termínů, aby bylo jejich záměr v tomto článku zjevnější.

  • Background Service: Typ BackgroundService.
  • Hostovaná služba: Implementace IHostedServicenebo samotné IHostedService.
  • Dlouhotrvající služba: Libovolná služba, která běží nepřetržitě.
  • služba systému Windows: Infrastruktura služby systému Windows, která byla původně zaměřená na rozhraní .NET Framework, ale nyní je přístupná prostřednictvím rozhraní .NET.
  • Pracovní služba: Šablona služby pracovního procesu.

Šablona pracovní služby

Šablona pracovní služby je k dispozici v rozhraní .NET CLI a sadě Visual Studio. Další informace najdete v tématu .NET CLI, dotnet new worker – šablona. Šablona se skládá z tříd Program a Worker.

using App.WorkerService;

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

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

Předchozí třída Program:

Výchozí hodnoty šablony

Šablona Pracovního procesu ve výchozím nastavení nepovoluje uvolňování paměti serveru( GC), protože existuje mnoho faktorů, které hrají roli při určování jeho potřeby. Všechny scénáře, které vyžadují dlouhotrvající služby, by měly zvážit dopad na výkon tohoto výchozího nastavení. Pokud chcete povolit GC serveru, přidejte do souboru projektu uzel ServerGarbageCollection:

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

kompromisy a aspekty

Povoleno Invalidní
Efektivní správa paměti: Automaticky uvolní nevyužitou paměť, aby se zabránilo nevrácené paměti a optimalizovalo využití prostředků. Vylepšený výkon v reálném čase: Vyhnout se možným prodlevám nebo narušením způsobeným sběrem paměti v aplikacích citlivých na latenci.
Dlouhodobá stabilita: Pomáhá udržovat stabilní výkon v dlouhodobých službách tím, že spravuje paměť v delších obdobích. Efektivita prostředků: Může šetřit prostředky procesoru a paměti v prostředích s omezenými zdroji.
Omezená údržba: Minimalizuje potřebu ruční správy paměti a zjednodušuje údržbu. Ruční řízení paměti: Poskytuje jemně odstupňovanou kontrolu nad pamětí pro specializované aplikace.
Předvídatelné chování: Přispívá k konzistentnímu a předvídatelnému chování aplikace. Vhodné pro krátkodobé procesy: Minimalizuje režii garbage collection (uvolňování paměti) pro krátkodobé nebo efemérní procesy.

Další informace o aspektech výkonu naleznete v tématu Server GC. Další informace o konfiguraci serveru GC naleznete v tématu Server GC konfigurační příklady.

třída Worker

Pokud jde o Worker, šablona poskytuje jednoduchou implementaci.

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

Předchozí třída Worker je podtřídou BackgroundService, která implementuje IHostedService. BackgroundService je abstract class a vyžaduje, aby podtřída implementovala BackgroundService.ExecuteAsync(CancellationToken). V implementaci šablony se smyčka ExecuteAsync spouští jednou za sekundu, zaznamenává aktuální datum a čas, dokud není proces signalizován k zrušení.

Soubor projektu

Šablona pracovního procesu spoléhá na následující soubor projektu Sdk:

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

Další informace naleznete v tématu sady projektů SDK .NET.

Balíček NuGet

Aplikace založená na šabloně Pracovního procesu používá sadu Microsoft.NET.Sdk.Worker SDK a obsahuje explicitní odkaz na balíček Microsoft.Extensions.Hosting.

Kontejnery a přizpůsobitelnost cloudu

U většiny moderních úloh .NET jsou kontejnery realizovatelnou možností. Při vytváření dlouhotrvající služby ze šablony Pracovního procesu v sadě Visual Studio se můžete přihlásit k podporu Dockeru. Tím se vytvoří Dockerfile, který kontejnerizuje vaši .NET aplikaci. Dockerfile je sada pokynů k sestavení obrazu. U aplikací .NET se Dockerfile obvykle nachází v kořenovém adresáři vedle souboru řešení.

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

Předchozí souboru Dockerfile kroky zahrnují:

  • Nastavení základního obrazu z mcr.microsoft.com/dotnet/runtime:8.0 jako aliasu base.
  • Změna pracovního adresáře na /app.
  • Nastavení aliasu build z obrázku mcr.microsoft.com/dotnet/sdk:8.0
  • Změna pracovního adresáře na /src.
  • Kopírování obsahu a publikování aplikace .NET:
  • Přeskládání obrazu sady .NET SDK z mcr.microsoft.com/dotnet/runtime:8.0 (alias base).
  • Kopírování publikovaného výstupu sestavení z /publish.
  • Definování vstupního bodu, který deleguje na dotnet App.BackgroundService.dll.

Návod

McR v mcr.microsoft.com znamená "Microsoft Container Registry" a je syndikovaný katalog kontejnerů Microsoftu z oficiálního centra Dockeru. Článek katalogu kontejnerů od Microsoftu obsahuje další podrobnosti.

Když cílíte na Docker jako strategii nasazení pro službu pracovního procesu .NET, je v souboru projektu potřeba vzít v úvahu několik aspektů:

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

V předchozím souboru projektu určuje prvek <DockerDefaultTargetOS> jako cíl Linux. Pokud chcete cílit na kontejnery Windows, použijte místo toho Windows. Microsoft.VisualStudio.Azure.Containers.Tools.Targets balíček NuGet se automaticky přidá jako odkaz na balíček, když vyberete podporu Dockeru ze šablony.

Další informace o Dockeru s .NET najdete v kurzu: Kontejnerizace aplikace .NET. Další informace o nasazení do Azure najdete v tématu Kurz: Nasazení pracovní služby do Azure.

Důležitý

Pokud chcete využít uživatelská tajemství se šablonou pracovníka, budete muset výslovně uvést balíček NuGet Microsoft.Extensions.Configuration.UserSecrets.

Rozšiřitelnost hostované služby

Rozhraní IHostedService definuje dvě metody:

Tyto dvě metody slouží jako životní cyklus metod – volají se během spouštění a zastavování událostí hostitele.

Poznámka

Při přepsání metod StartAsync nebo StopAsync je nutné volat a await metodu třídy base, aby se zajistilo správné spuštění nebo vypnutí služby.

Důležitý

Rozhraní slouží jako omezení parametru obecného typu pro metodu rozšíření AddHostedService<THostedService>(IServiceCollection), což znamená, že jsou povoleny pouze implementace. Můžete používat poskytnuté BackgroundService s podtřídou nebo zcela implementovat vlastní.

Dokončení signálu

Ve většině běžných scénářů nemusíte explicitně signalizovat dokončení hostované služby. Když hostitel spustí služby, jsou navržené tak, aby běžely, dokud se hostitel nezastaví. V některých scénářích ale možná budete muset po dokončení služby signalizovat dokončení celé hostitelské aplikace. Chcete-li signalizovat dokončení, zvažte následující Worker třídu:

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

V předchozím kódu metoda BackgroundService.ExecuteAsync(CancellationToken) neprovádí smyčku a po svém dokončení zavolá IHostApplicationLifetime.StopApplication().

Důležitý

To bude signalizovat hostiteli, že by se měl zastavit, a bez tohoto volání na StopApplication bude hostitel dál běžet donekonečna. Pokud máte v úmyslu spustit krátkodobou hostovanou službu (spustitelný jednou) a chcete použít šablonu Pracovního procesu, musíte zavolat StopApplication, aby signalizovali hostiteli zastavení.

Další informace najdete tady:

Alternativní přístup

Pro dočasnou aplikaci, která potřebuje injektáž závislostí, protokolování a konfiguraci, použijte místo šablony Worker obecný hostitel .NET. Díky tomu můžete tyto funkce používat bez Worker třídy. Jednoduchý příklad krátkodobé aplikace používající obecného hostitele může definovat soubor projektu takto:

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

Třída Program by mohla vypadat nějak takto:

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
}

Předchozí kód vytvoří JobRunner službu, což je vlastní třída, která obsahuje logiku pro spuštění úlohy. Metoda RunAsync je volána na JobRunner, a pokud se úspěšně dokončí, aplikace vrátí 0. Pokud dojde k neošetřené výjimce, zaznamená chybu a vrátí .1

V tomto jednoduchém JobRunner scénáři by třída mohla vypadat takto:

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

Samozřejmě byste do metody museli přidat skutečnou logiku RunAsync , ale tento příklad ukazuje, jak použít obecného hostitele pro krátkodobou aplikaci bez nutnosti Worker třídy a bez nutnosti explicitně signalizovat dokončení hostitele.

Viz také