建立持續運行的服務有許多原因,例如:
- 處理需要大量 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 Service:BackgroundService 類型。
- 託管服務:IHostedService 的實作或 IHostedService 本身。
- 長時間執行的服務: 持續執行的任何服務。
- Windows 服務:Windows 服務 基礎結構,最初是以 .NET Framework 為中心,但現在可透過 .NET 進行存取。
- 背景工作服務:背景工作服務 範本。
Worker服務範本
工作者服務範本可在 .NET CLI 與 Visual Studio 中使用。 如需詳細資訊,請參閱 .NET CLI,dotnet new worker
- 範本。 範本是由 Program
和 Worker
類別所組成。
using App.WorkerService;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
IHost host = builder.Build();
host.Run();
上述 Program
類別:
- 建立 HostApplicationBuilder。
- 呼叫 AddHostedService,將
Worker
註冊為託管服務。 - 從生成器生成 IHost。
- 在執行應用程式的
Run
實例上呼叫host
。
範本預設值
工作範本預設不會啟用伺服器垃圾回收(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的子類別,它會實作 IHostedService。
BackgroundService 是 abstract 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 應用程式:
- 應用程式是使用
dotnet publish
命令發行。
- 應用程式是使用
- 從
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 介面會定義兩種方法:
這兩種方法作為 生命週期 方法—它們分別在主機啟動和停止事件時被呼叫。
註
當覆寫 StartAsync 或 StopAsync 方法時,您必須呼叫並執行 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
建立服務,這是自定義類別,其中包含要執行之作業的邏輯。 在 RunAsync
上 JobRunner
呼叫 方法,如果成功完成,應用程式會傳 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 實作: