共用方式為


.NET 中的工作者服務

建立持續運行的服務有許多原因,例如:

  • 處理需要大量 CPU 的數據。
  • 在背景中排列工作項目。
  • 依排程執行以時間為基礎的作業。

背景服務處理通常不會牽涉到使用者介面 (UI),但 UI 可以建置在它們周圍。 在 .NET Framework 的早期,Windows 開發人員可以針對這些目的建立 Windows 服務。 現在使用 .NET,您可以使用 BackgroundService,這是 IHostedService的實作,或實作您自己的 。

使用 .NET 時,您不再受限於 Windows。 您可以開發跨平臺背景服務。 託管服務已準備好用於記錄、設定和相依性注入(DI)。 它們是連結庫擴充套件的一部分,這表示它們對所有使用 泛型主機的 .NET 工作負載而言都是基本的。

重要

安裝 .NET SDK 也會安裝 Microsoft.NET.Sdk.Worker 和背景工作範本。 換句話說,安裝 .NET SDK 之後,您可以使用 dotnet new worker 命令來建立新的工作程序。 如果您使用 Visual Studio,範本會隱藏,直到安裝選擇性 ASP.NET 和 Web 開發工作負載為止。

術語

許多詞彙錯誤地使用同義字。 本節會定義其中一些詞彙,讓本文的意圖更加明顯。

  • Background ServiceBackgroundService 類型。
  • 託管服務IHostedService 的實作或 IHostedService 本身。
  • 長時間執行的服務: 持續執行的任何服務。
  • Windows 服務Windows 服務 基礎結構,最初是以 .NET Framework 為中心,但現在可透過 .NET 進行存取。
  • 背景工作服務背景工作服務 範本。

Worker服務範本

工作者服務範本可在 .NET CLI 與 Visual Studio 中使用。 如需詳細資訊,請參閱 .NET CLI,dotnet new worker - 範本。 範本是由 ProgramWorker 類別所組成。

using App.WorkerService;

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

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

上述 Program 類別:

範本預設值

工作範本預設不會啟用伺服器垃圾回收(GC),因為有許多因素決定其是否必要。 所有需要長時間執行服務的案例都應該考慮此預設值的效能影響。 若要啟用伺服器 GC,請將 ServerGarbageCollection 節點新增至項目檔:

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

取捨和考慮

啟用 已停用
有效率的記憶體管理:自動回收未使用的記憶體,以防止記憶體流失並優化資源使用量。 改善即時效能:避免延遲敏感應用程式中垃圾收集所造成的潛在暫停或中斷。
長期穩定性:藉由管理長時間的記憶體,協助維護長期執行服務中的穩定效能。 資源效率:可在資源限制的環境中節省CPU和記憶體資源。
減少維護:將手動記憶體管理的需求降到最低,簡化維護。 手動記憶體控制:為特殊化應用程式提供更精細的記憶體控制。
可預測的行為:有助於一致且可預測的應用程式行為。 適用於短期進程:將短期或暫時進程的垃圾收集額外負荷降到最低。

如需效能考慮的詳細資訊,請參閱 Server GC。 如需設定伺服器 GC 的詳細資訊,請參閱 伺服器 GC 組態範例。

工作者類別

至於 Worker,範本提供簡單的實現。

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

Worker 類別是 BackgroundService的子類別,它會實作 IHostedServiceBackgroundServiceabstract class,需要子類別(即 subclass)實作 BackgroundService.ExecuteAsync(CancellationToken)。 在範本實作中,ExecuteAsync 每秒迴圈一次,記錄目前日期和時間,直到程式發出取消訊號為止。

項目檔

工作範本依賴下列專案檔案 Sdk

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

如需詳細資訊,請參閱 .NET 專案 SDK

NuGet 套件

以背景工作者範本為基礎的應用程式使用 Microsoft.NET.Sdk.Worker SDK,並具有對 Microsoft.Extensions.Hosting 套件的明確引用。

容器和雲端適應性

使用最新的 .NET 工作負載時,容器是可行的選項。 從 Visual Studio 的 Worker 範本建立長時間執行的服務時,您可以選擇加入 Docker 支援。 這麼做會建立一個 Dockerfile,以容器化您的 .NET 應用程式。 Dockerfile 是一組建置映射的指示。 針對 .NET 應用程式,Dockerfile 通常位於方案檔旁邊的目錄根目錄中。

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

上述 Dockerfile 步驟包括:

  • 將來自 mcr.microsoft.com/dotnet/runtime:8.0 的基底映像設定為別名 base
  • 將工作目錄變更為 /app
  • build 映像設定 mcr.microsoft.com/dotnet/sdk:8.0 別名。
  • 將工作目錄變更為 /src
  • 複製內容並發佈 .NET 應用程式:
  • mcr.microsoft.com/dotnet/runtime:8.0 轉寄 .NET SDK 映射(base 別名)。
  • /publish複製已發佈的組建輸出。
  • 將進入點定義為委派給 dotnet App.BackgroundService.dll

提示

mcr.microsoft.com 中的 MCR 代表“Microsoft Container Registry”,這是 Microsoft 從官方 Docker 中樞發布的聯合容器目錄。 Microsoft syndicates 容器目錄 文章包含其他詳細數據。

當您將 Docker 作為 .NET 背景工作服務的部署策略時,專案檔中有幾個需要考慮的因素:

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

在上述項目檔中,<DockerDefaultTargetOS> 專案會將 Linux 指定為其目標。 若要以 Windows 容器為目標,請改用 Windows。 從範本選取 Microsoft.VisualStudio.Azure.Containers.Tools.Targets 時, NuGet 套件 會自動新增為套件參考。

如需 Docker with .NET 的詳細資訊,請參閱 教學課程:將 .NET 應用程式容器化。 如需有關部署到 Azure 的詳細資訊,請參閱 教程:將工作服務部署到 Azure

重要

如果您想要在背景工作範本中使用使用者機密,則必須明確引用Microsoft.Extensions.Configuration.UserSecrets NuGet 套件。

託管服務擴充性

IHostedService 介面會定義兩種方法:

這兩種方法作為 生命週期 方法—它們分別在主機啟動和停止事件時被呼叫。

當覆寫 StartAsyncStopAsync 方法時,您必須呼叫並執行 await 類別的 base 方法,以確保服務能夠正常啟動和/或關閉。

重要

介面在 AddHostedService<THostedService>(IServiceCollection) 擴充方法上做為泛型型別參數條件約束,這表示只允許實作。 您可以自由地使用提供的 BackgroundService 搭配子類別,或完全實作您自己的類別。

訊號完成

在最常見的案例中,您不需要明確發出託管服務完成的訊號。 當主機啟動服務時,其設計目的是要執行,直到主機停止為止。 不過,在某些情況下,您可能需要在服務完成時發出整個主應用程式完成的訊號。 若要發出完成訊號,請考慮下列 Worker 類別:

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

在上述程式代碼中,BackgroundService.ExecuteAsync(CancellationToken) 方法不會迴圈,而且完成時會呼叫 IHostApplicationLifetime.StopApplication()

重要

這會向主機發出訊號,指出它應該停止,而若不呼叫StopApplication,主機將繼續持續運行。 如果您想要執行一次性的託管服務(執行一次案例),而且想要使用工作者範本,您必須呼叫 StopApplication 來通知主機停止。

如需詳細資訊,請參閱:

替代方法

對於需要相依性插入、記錄和設定的短暫應用程式,請使用 .NET 泛型主機 ,而不是 .NET 工作模板。 這能讓您在沒有Worker類別的情況下使用這些功能。 使用泛型主機的簡短應用程式範例可能會定義項目檔,如下所示:

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

類別 Program 看起來可能如下所示:

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
}

上述程式代碼會 JobRunner 建立服務,這是自定義類別,其中包含要執行之作業的邏輯。 在 RunAsyncJobRunner呼叫 方法,如果成功完成,應用程式會傳 0回 。 如果發生未處理的例外狀況,它會記錄錯誤並傳 1回 。

在此簡單案例中,類別 JobRunner 看起來會像這樣:

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

您顯然需要將實際邏輯新增至 RunAsync 方法,但此範例示範如何在不需要 Worker 類別的情況下,使用泛型主機來執行短期應用程式,同時也不需要明確發出主機完成的訊號。

另請參閱

  • BackgroundService 子類教學:
    • 在 .NET 中建立佇列服務
    • BackgroundService 中使用 範圍內的服務
    • BackgroundService 中使用 建立 Windows 服務
  • 自定義 IHostedService 實作:
    • IHostedService 中實作 介面