C#中继承BackgroundService的服务无法手动停止,如何用stoppingToken解决
正确停止继承自BackgroundService的服务
问题场景
你尝试通过自定义方法停止一个继承自BackgroundService的ExampleService,代码中通过设置stopRequested标志并调用StopAsync,但服务无法正常停止。你希望仅停止该服务而非终止整个应用,且明确需要通过ExecuteAsync中的stoppingToken来实现停止逻辑。
原代码问题分析
- 线程可见性问题:
stopRequested字段未做线程安全处理,多线程环境下可能无法及时感知到标志变化 - 未利用内置取消机制:手动调用
StopAsync不会自动触发stoppingToken的取消信号,原循环依赖的双重判断无法正确响应停止请求 - 逻辑冗余且不可靠:自定义标志与内置取消token分离,既增加复杂度,也容易出现同步问题
正确实现方案
通过创建手动取消令牌源,并与BackgroundService的内置stoppingToken合并,统一管理应用关闭和手动停止两种信号,实现可靠的服务停止逻辑。
修改后的ExampleService代码
public class ExampleService : BackgroundService, IExampleService { private readonly ILogger<ExampleService> _logger; private readonly CancellationTokenSource _manualStopCts = new(); public ExampleService(ILogger<ExampleService> logger) { _logger = logger; } public async Task StopAsync(CancellationToken token = default) { _logger.LogInformation("收到服务停止请求"); _manualStopCts.Cancel(); // 等待服务执行逻辑完成 await base.StopAsync(token); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // 合并应用停止令牌与手动停止令牌 using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, _manualStopCts.Token); var cancellationToken = linkedCts.Token; var count = 0; while (!cancellationToken.IsCancellationRequested) { try { await Task.Delay(1000, cancellationToken); _logger.LogInformation("Service is working {0}", count++); } catch (OperationCanceledException) { _logger.LogInformation("服务停止信号已触发,开始退出"); break; } } } public override void Dispose() { _manualStopCts.Dispose(); base.Dispose(); } } public interface IExampleService { Task StopAsync(CancellationToken token = default); }
修改后的API调用代码
[HttpGet("stoptask2")] public async Task<IActionResult> StopService() { await _exampleService.StopAsync(); return Ok("服务停止请求已提交"); }
关键实现说明
- 合并取消令牌:使用
CreateLinkedTokenSource将应用内置的stoppingToken和手动创建的_manualStopCts.Token合并,确保两种停止信号都能触发服务退出 - 线程安全的取消信号:取消令牌的触发是线程安全的,无需额外处理可见性问题
- 正确捕获取消异常:
Task.Delay会在令牌取消时抛出OperationCanceledException,捕获后可优雅退出循环 - 资源释放:重写
Dispose方法释放_manualStopCts,避免资源泄漏
内容的提问来源于stack exchange,提问作者Okan Karadag
相关产品推荐
相关产品推荐

