关于 worker-services
用于实现 .NET 长时间运行后台服务和定时 Worker 的开发技能,涵盖托管服务生命周期、取消处理、作用域依赖、队列处理、健康检查和优雅关闭。
主要功能
- 01使用 BackgroundService 管理 Worker 生命周期
- 02为作用域依赖创建服务作用域
- 03传递取消令牌并实现优雅关闭
- 04使用 PeriodicTimer 安排任务
- 05添加健康检查与结构化日志
适用场景
- 01构建定时后台 Worker
- 02使用作用域服务处理队列任务
- 03为 Worker 配置健康监控和关闭流程
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ managedcode/ dotnet-skills --skill worker-services安装后在 Claude Code 中输入/worker-services确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
manifest.jsonreferences/anti-patterns.mdpatterns.md详细介绍
.NET Worker Services
触发条件
- 构建长时间运行的后台服务或计划任务 Worker
- 向应用添加托管服务,或将其提取为 Worker 进程
- 检查优雅关闭、取消、队列处理或运行状况行为
文档
- .NET 中的 Worker Services
- 在 ASP.NET Core 中使用托管服务执行后台任务
- 使用 BackgroundService 创建 Windows Service
- .NET 中的应用运行状况检查
- ASP.NET Core 中的运行状况检查
参考资料
- patterns.md - BackgroundService 模式、优雅关闭和运行状况检查实现
- anti-patterns.md - Worker 服务中的常见错误及避免方法
工作流
-
使用 BackgroundService 作为基类:
- 提供标准的
StartAsync/StopAsync处理 - 只需专注于实现
ExecuteAsync - 内置适当的取消令牌管理
- 提供标准的
-
正确处理作用域依赖项:
- 为作用域服务创建服务作用域
- 托管服务默认不会创建作用域
-
实现优雅关闭:
- 传递取消令牌
- 令牌触发后及时完成工作
- 避免超时时发生非优雅关闭
-
保持执行循环精简:
- 将业务逻辑移至可测试的服务中
- 处理异常以避免服务崩溃
- 使用
PeriodicTimer执行计划任务
-
添加可观测性:
- 使用运行状况检查确认就绪状态和存活状态
- 暴露指标并使用结构化日志记录
- 对于多实例部署,可考虑使用分布式锁
当前上游说明
.NET runtimev10.0.11正在提供维护更新。对于 Worker,升级后应重新检查 cgroup-v2 内存限制检测、取消、优雅关闭、长时间运行线程的线程静态数据和 GC 路径、相关场景下的 NativeAOT/WASM 启动,以及 Windows 目录枚举;默认情况下不要更改架构。- 添加新的 Worker 入口点时,请使用更新后的 Worker Services 和托管服务 Learn 页面,确认当前准确的托管和运行状况检查 API。
基本 BackgroundService 模式
简单 Worker
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
public Worker(ILogger<Worker> logger)
{
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Worker starting");
while (!stoppingToken.IsCancellationRequested)
{
try
{
_logger.LogInformation("Worker running at: {Time}", DateTimeOffset.Now);
await DoWorkAsync(stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
// Graceful shutdown, not an error
break;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error in worker iteration");
// Continue or break based on error severity
}
}
_logger.LogInformation("Worker stopping");
}
private async Task DoWorkAsync(CancellationToken cancellationToken)
{
// Business logic here
}
}
使用 PeriodicTimer(推荐)
public class TimedWorker : BackgroundService
{
private readonly ILogger<TimedWorker> _logger;
private readonly IServiceScopeFactory _scopeFactory;
private readonly TimeSpan _period = TimeSpan.FromMinutes(1);
public TimedWorker(ILogger<TimedWorker> logger, IServiceScopeFactory scopeFactory)
{
_logger = logger;
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
using var timer = new PeriodicTimer(_period);
while (await timer.WaitForNextTickAsync(stoppingToken))
{
try
{
await using var scope = _scopeFactory.CreateAsyncScope();
var processor = scope.ServiceProvider.GetRequiredService<IDataProcessor>();
await processor.ProcessAsync(stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing scheduled task");
}
}
}
}
处理作用域依赖项
使用 Scope Factory 的正确模式
public class ScopedWorker : BackgroundService
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly ILogger<ScopedWorker> _logger;
public ScopedWorker(IServiceScopeFactory scopeFactory, ILogger<ScopedWorker> logger)
{
_scopeFactory = scopeFactory;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
// Create scope for each unit of work
await using var scope = _scopeFactory.CreateAsyncScope();
var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var service = scope.ServiceProvider.GetRequiredService<IScopedService>();
await service.ProcessAsync(dbContext, stoppingToken);
await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken);
}
}
}
队列处理模式
消息队列 Worker
public class QueueWorker : BackgroundService
{
private readonly ILogger<QueueWorker> _logger;
private readonly IServiceScopeFactory _scopeFactory;
private readonly IBackgroundTaskQueue _taskQueue;
public QueueWorker(
ILogger<QueueWorker> logger,
IServiceScopeFactory scopeFactory,
IBackgroundTaskQueue taskQueue)
{
_logger = logger;
_scopeFactory = scopeFactory;
_taskQueue = taskQueue;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Queue Worker started");
while (!stoppingToken.IsCancellationRequested)
{
var workItem = await _taskQueue.DequeueAsync(stoppingToken);
try
{
await using var scope = _scopeFactory.CreateAsyncScope();
await workItem(scope.ServiceProvider, stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error processing queued work item");
// Handle poison message - retry, dead-letter, etc.
}
}
}
}
// Task queue interface
public interface IBackgroundTaskQueue
{
ValueTask QueueBackgroundWorkItemAsync(
Func<IServiceProvider, CancellationToken, ValueTask> workItem);
ValueTask<Func<IServiceProvider, CancellationToken, ValueTask>> DequeueAsync(
CancellationToken cancellationToken);
}
Worker 的运行状况检查
添加运行状况检查端点
// Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddHostedService<Worker>();
// Add health checks
builder.Services.AddHealthChecks()
.AddCheck<WorkerHealthCheck>("worker_health")
.AddResourceUtilizationHealthCheck();
// Add HTTP endpoint for health checks
builder.Services.AddHealthChecksUI();
// Or use simple TCP listener for Kubernetes
builder.Services.AddSingleton<TcpHealthProbeService>();
builder.Services.AddHostedService(sp => sp.GetRequiredService<TcpHealthProbeService>());
var host = builder.Build();
host.Run();
自定义运行状况检查
public class WorkerHealthCheck : IHealthCheck
{
private readonly WorkerState _workerState;
public WorkerHealthCheck(WorkerState workerState)
{
_workerState = workerState;
}
public Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
if (_workerState.LastSuccessfulRun > DateTime.UtcNow.AddMinutes(-5))
{
return Task.FromResult(HealthCheckResult.Healthy(
$"Last successful run: {_workerState.LastSuccessfulRun}"));
}
return Task.FromResult(HealthCheckResult.Unhealthy(
$"No successful run since: {_workerState.LastSuccessfulRun}"));
}
}
// Shared state
public class WorkerState
{
public DateTime LastSuccessfulRun { get; set; } = DateTime.UtcNow;
public bool IsProcessing { get; set; }
}
优雅关闭模式
正确处理关闭
public class GracefulWorker : BackgroundService
{
private readonly ILogger<GracefulWorker> _logger;
private int _currentWorkItemId;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Worker starting");
while (!stoppingToken.IsCancellationRequested)
{
_currentWorkItemId = GetNextWorkItemId();
try
{
// Pass cancellation token to all async operations
await ProcessWorkItemAsync(_currentWorkItemId, stoppingToken);
}
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
{
_logger.LogInformation(
"Shutdown requested, stopping after work item {Id}", _currentWorkItemId);
break;
}
}
}
public override async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Worker stopping gracefully");
await base.StopAsync(cancellationToken);
_logger.LogInformation("Worker stopped");
}
}
Windows Service 部署
配置为 Windows Service
// Program.cs
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddWindowsService(options =>
{
options.ServiceName = "My Worker Service";
});
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
host.Run();
项目文件设置
<Project Sdk="Microsoft.NET.Sdk.Worker">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
</PropertyGroup>
</Project>
最佳实践
- 使用 BackgroundService 作为基类 - 处理
StartAsync/StopAsync的样板代码和取消管理 - 为作用域依赖项创建作用域 - 使用
IServiceScopeFactory解析 DbContext 等作用域服务 - 在所有地方传递取消令牌 - 将其传递给所有异步方法,以便快速关闭
- 使用 try-catch 包裹工作 - 未处理的异常会导致服务完全停止
- 使用 PeriodicTimer 执行定时任务 - 相比
Task.Delay更简洁,并且能正确支持取消 - 添加运行状况检查 - 这是 Kubernetes 存活/就绪探测的必要条件
- 避免阻塞 StartAsync - 长时间的初始化会延迟其他托管服务的启动
- 重写方法时调用基类方法 - 始终调用
await base.StartAsync()和await base.StopAsync() - 将 Windows Service 发布为单文件 - 降低部署的复杂性和出错概率
- 考虑扩缩容要求 - 如果需要独立扩缩容,请拆分工作进程项目
应避免的反模式
| 反模式 | 不良影响 | 更好的做法 |
|---|---|---|
临时编写的 while(true) 循环 | 无法平稳关闭,生命周期管理不佳 | 使用 BackgroundService |
| 忽略取消令牌 | 无法平稳关闭,导致资源泄漏 | 将令牌传递给所有异步调用 |
| 直接注入作用域服务 | 依赖项被长期持有,导致内存泄漏 | 使用 IServiceScopeFactory |
ExecuteAsync 中出现未处理的异常 | 工作进程会静默停止 | 使用 try-catch 包裹,记录日志并继续 |
StartAsync 长时间运行 | 阻止其他服务启动 | 将工作移至 ExecuteAsync |
async void 方法 | 异常会导致进程崩溃 | 使用 async Task |
| 缺少运行状况检查 | 无法了解工作进程状态 | 实现 IHealthCheck |
| 使用紧密循环轮询 | 浪费 CPU,响应迟缓 | 使用 PeriodicTimer 或事件驱动方式 |
未重写 StopAsync | 错过清理机会 | 重写该方法以执行平稳清理 |
| 单例 DbContext | 非线程安全,数据过时 | 每次操作都创建作用域 |
交付内容
- 行为良好的工作进程和托管服务
- 可预测的启动和关闭行为
- 正确处理作用域依赖项
- 用于生产环境可观测性的运行状况检查
- 针对队列工作的重试和毒消息处理
验证
- 已传递取消令牌,并遵循关闭请求
- 在正确的作用域内解析作用域服务
- 异常处理可避免服务崩溃
- 运行状况检查准确报告工作进程状态
- 可通过日志或遥测查看运行时行为
- 异步上下文中没有阻塞调用
评论
支持 Markdown