.NET Core Worker Service实现长时间运行操作:创建、取消、暂停与恢复
.NET Worker Service 取消、暂停与结果存储最佳实践及开源示例
核心功能实现最佳实践
任务取消
- 基于.NET原生
CancellationToken机制实现,为每个任务关联唯一的CancellationTokenSource(CTS),并通过内存字典或分布式缓存存储任务ID与CTS的映射。 - API接收取消请求时,根据任务ID找到对应的CTS并调用
Cancel(),任务执行过程中定期检查IsCancellationRequested,优雅终止流程并释放资源。 - 代码示例:
// 存储任务取消令牌源(生产环境建议用分布式缓存替代内存字典) private readonly Dictionary<Guid, CancellationTokenSource> _taskCtsMap = new(); // 启动任务时绑定CTS public async Task EnqueueLongRunningTaskAsync(Guid taskId) { var cts = new CancellationTokenSource(); _taskCtsMap.TryAdd(taskId, cts); await _backgroundTaskQueue.QueueBackgroundWorkItemAsync(async token => { try { await ExecuteLongRunningTask(taskId, cts.Token); } finally { _taskCtsMap.TryRemove(taskId, out _); cts.Dispose(); } }); } // 处理取消请求 public void CancelTask(Guid taskId) { if (_taskCtsMap.TryGetValue(taskId, out var cts)) cts.Cancel(); }
任务暂停/恢复
- 使用
ManualResetEventSlim作为暂停信号量,为每个任务维护一个实例,初始状态设为Set()(非暂停)。 - 任务执行片段中插入信号量等待逻辑,触发暂停时调用
Reset()阻塞任务,恢复时调用Set()继续执行。注意结合CancellationToken避免无限阻塞。 - 代码示例:
private readonly Dictionary<Guid, ManualResetEventSlim> _taskPauseEvents = new(); // 启动任务时初始化暂停事件 public async Task EnqueueTaskWithPauseSupport(Guid taskId) { var pauseEvent = new ManualResetEventSlim(true); _taskPauseEvents.TryAdd(taskId, pauseEvent); await _backgroundTaskQueue.QueueBackgroundWorkItemAsync(async token => { try { while (!token.IsCancellationRequested) { pauseEvent.Wait(token); // 检查暂停状态,支持取消 await ExecuteTaskSegmentAsync(token); } } finally { _taskPauseEvents.TryRemove(taskId, out _); pauseEvent.Dispose(); } }); } // 暂停任务 public void PauseTask(Guid taskId) { if (_taskPauseEvents.TryGetValue(taskId, out var evt)) evt.Reset(); } // 恢复任务 public void ResumeTask(Guid taskId) { if (_taskPauseEvents.TryGetValue(taskId, out var evt)) evt.Set(); }
结果与状态存储
- 设计任务状态表记录全生命周期数据,核心字段包括:
TaskId(主键)、Status(枚举:待执行/运行中/已取消/已完成/失败)、Progress(进度百分比)、Result(JSON格式存储结果)、ErrorMessage、CreatedAt、UpdatedAt。 - 任务执行过程中定期更新进度和状态,完成或失败时写入最终结果;API层可通过查询该表返回任务状态和结果。
- 代码示例(EF Core):
public async Task UpdateTaskProgressAsync(Guid taskId, int progress, string? status = null) { await _dbContext.TaskStatuses .Where(t => t.TaskId == taskId) .ExecuteUpdateAsync(s => s .SetProperty(t => t.Progress, progress) .SetProperty(t => t.UpdatedAt, DateTime.UtcNow) .SetPropertyIfNotNull(t => t.Status, status)); }
开源参考项目
- Hangfire:成熟的后台任务管理库,内置任务取消、状态跟踪、结果存储功能,支持SQL Server、Redis等多种存储后端,可无缝集成到Worker Service中。
- MassTransit:与RabbitMQ深度集成的分布式消息框架,通过Saga模式实现复杂任务的暂停、恢复与状态管理,适合分布式场景下的任务处理。
- Microsoft Background Tasks Sample:官方提供的后台任务队列示例,包含任务取消、队列管理的基础实现,可作为扩展暂停和结果存储功能的起点。
内容的提问来源于stack exchange,提问作者Praveen Kumar N
相关产品推荐
相关产品推荐

