.NET 中的背景工作服務

建立長時間執行的服務有很多原因,例如:

  • 處理 CPU 密集資料。
  • 在背景工作中佇列工作項目。
  • 依排程執行以時間為基礎的作業。

背景服務處理通常不包括使用者介面 (UI),但 UI 可以建置在其周圍。 在早期 .NET 架構時代,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 開發工作負載前,範本將會隱藏。

詞彙

許多詞彙皆會被誤用為同義字。 本節中部分詞彙的定義可讓意圖更為明顯。

  • 背景服務:參考 BackgroundService 類型。
  • 託管服務IHostedService 的實作,或參考 IHostedService 本身。
  • 長時間執行的服務: 任何持續執行的服務。
  • Windows 服務:「Windows 服務」 基礎結構,原本以 .NET 架構為主,但現在可透過 .NET 存取。
  • 背景工作服務:參考「背景工作角色服務」範本。

背景工作服務範本

背景工作角色服務範本可供 .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 和記憶體資源。
減少維護:將手動記憶體管理的需求降到最低,進而簡化維護。 手動記憶體控制:為特殊化應用程式提供更精細的記憶體控制。
可預測的行為:有助於實現一致且可預測的應用程式行為。 適用於短期流程:將短期或暫時流程的記憶體回收額外負荷降到最低。

如需效能考量的詳細資訊,請參閱伺服器 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,而且需要子類別才能實作 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 中從背景工作角色範本建立長時間執行的服務時,您可以選擇加入 Docker 支援。 這麼做會建立可將 .NET 應用程式容器化的 DockerfileDockerfile 是建置映像的一組指令。 .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 AS base
WORKDIR /app

FROM mcr.microsoft.com/dotnet/sdk:8.0 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
  • mcr.microsoft.com/dotnet/sdk:8.0 映像設定 build 別名。
  • 將工作目錄變更為 /src
  • 複製內容並發佈 .NET 應用程式:
  • mcr.microsoft.com/dotnet/runtime:8.0 (base 別名) 轉送 .NET SDK 映像。
  • /publish 複製已發佈的建置輸出。
  • 定義委派給 dotnet App.BackgroundService.dll 的進入點。

提示

mcr.microsoft.com 中的 MCR 是 "Microsoft Container Registry" 的縮寫,而且是 Microsoft 官方 Docker 中樞同步發佈的容器目錄。 Microsoft 訂閱容器目錄文章包含其他詳細資料。

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

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

如需 Docker 和 .NET 的詳細資訊,請參閱教學課程:將 .NET 應用程式容器化。 如需部署至 Azure 的詳細資訊,請參閱教學課程:將背景工作角色服務部署至 Azure

重要

如果您想要搭配背景工作角色範本利用「使用者祕密」,則必須明確參考 Microsoft.Extensions.Configuration.UserSecrets NuGet 套件。

託管服務擴充性

IHostedService 介面會定義兩種方法:

這兩種方法可當作「生命週期」方法 - 分別在主機啟動和停止事件期間呼叫。

注意

覆寫 StartAsyncStopAsync 方法時,您必須呼叫和 awaitbase 類別方法,以確保服務正常啟動和/或關閉。

重要

介面可作為 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();
    }
}

在上述程式碼中,ExecuteAsync方法不會重複進行,而且完成時會呼叫 IHostApplicationLifetime.StopApplication()

重要

這會向主機傳送停止訊號,而且若沒有對主機傳送此 StopApplication 呼叫,主機將會無限期執行。

如需詳細資訊,請參閱

另請參閱