如何在IHostedService中正确实现任务启动、取消与停止逻辑
问题根源梳理
- 类型定义错误:
workersToRun声明为List<IWorker>但实际存储的是Task对象,类型不匹配 - Worker1 实现错误:
DoWork方法第一次循环就直接return Task.Delay(1000),循环逻辑完全不生效,自然不会检测取消令牌状态 - 令牌拼写错误:接口定义中
CancelationToken拼写错误,正确为CancellationToken - 任务跟踪缺失:
RunTasks中调用Task.WhenAll后没有持有返回的总任务对象,也没有等待逻辑,StopAsync 时无法追踪所有worker的运行状态 - 停止逻辑缺失:
StopAsync直接返回完成状态,没有等待所有worker执行清理逻辑就结束,主机关闭时会直接终止未完成的后台任务 - 命名冲突风险:自定义类名和.NET自带的
BackgroundService抽象类重名,容易引发编译或运行异常
修正后完整实现
1. Worker接口与实现修正
public interface IWorker { Task DoWork(CancellationToken token); } public class Worker1 : IWorker { public async Task DoWork(CancellationToken token) { while (!token.IsCancellationRequested) { // 必须await延迟任务才会进入下一轮循环,令牌传入Delay可以在取消时立刻触发中断 await Task.Delay(1000, token); // 后台业务逻辑 } // 清理逻辑 Console.WriteLine("Worker1 清理完成"); } } public class Worker2 : IWorker { public async Task DoWork(CancellationToken token) { while (!token.IsCancellationRequested) { await Task.Delay(1000, token); // 后台异步业务逻辑 } // 清理逻辑 Console.WriteLine("Worker2 清理完成"); } }
2. 后台服务实现修正
// 重命名避免和系统自带类冲突 public class BackgroundWorkerService : IHostedService, IDisposable { private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private List<Task> _runningWorkerTasks; public Task StartAsync(CancellationToken cancellationToken) { _runningWorkerTasks = RunTasks(_cts.Token); return Task.CompletedTask; } private List<Task> RunTasks(CancellationToken cancellationToken) { var workers = new List<IWorker> { new Worker1(), new Worker2() // 实际生产环境可直接从DI容器获取所有IWorker实例 }; // 启动所有worker并存储对应的运行任务,用于后续停止时跟踪 return workers.Select(worker => Task.Run(() => worker.DoWork(cancellationToken), cancellationToken)).ToList(); } public async Task StopAsync(CancellationToken cancellationToken) { try { // 触发取消信号,通知所有worker退出循环 _cts.Cancel(); } finally { // 等待所有worker完成清理,或者触发主机关闭超时信号后终止等待 await Task.WhenAny(Task.WhenAll(_runningWorkerTasks), Task.Delay(Timeout.Infinite, cancellationToken)); } } public void Dispose() { _cts?.Cancel(); _cts?.Dispose(); } }
3. 服务注册修正
services.AddHostedService<BackgroundWorkerService>();
关键逻辑说明
- 取消信号会同时传递给
Task.Delay和worker的循环判断,收到取消指令后会立刻中断当前等待,进入清理逻辑 StopAsync传入的cancellationToken是主机关闭超时触发信号,默认超时时间为5秒,如果worker清理超过这个时间会直接终止等待,避免服务卡住无法关闭- 所有worker的运行任务都被统一存储,停止时可以确保清理逻辑执行完成后再结束服务进程
内容的提问来源于stack exchange,提问作者Berrepoot
相关产品推荐
相关产品推荐

