You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#中继承BackgroundService的服务无法手动停止,如何用stoppingToken解决

正确停止继承自BackgroundService的服务

问题场景

你尝试通过自定义方法停止一个继承自BackgroundService的ExampleService,代码中通过设置stopRequested标志并调用StopAsync,但服务无法正常停止。你希望仅停止该服务而非终止整个应用,且明确需要通过ExecuteAsync中的stoppingToken来实现停止逻辑。

原代码问题分析

  1. 线程可见性问题:stopRequested字段未做线程安全处理,多线程环境下可能无法及时感知到标志变化
  2. 未利用内置取消机制:手动调用StopAsync不会自动触发stoppingToken的取消信号,原循环依赖的双重判断无法正确响应停止请求
  3. 逻辑冗余且不可靠:自定义标志与内置取消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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 05:01:19