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

Worker Service执行间隔动态变更:如何取消运行中Task.Delay以应用新间隔

实现方案

核心思路是新增独立的取消令牌源控制延迟任务,配置变更时主动取消当前等待的延迟,立即应用新间隔。

步骤1:新增私有字段控制延迟取消

在基类中添加专门用于控制延迟任务的取消令牌源字段:

private CancellationTokenSource? _delayCts;

步骤2:修改配置变更回调

配置更新时除了修改间隔值,主动触发当前延迟的取消:

public virtual void UpdateOptions(TWorkerOptions options)
{
    Interval = options.Interval;
    // Cancel方法支持多线程调用,空值判断避免空引用
    _delayCts?.Cancel();
}

步骤3:改写延迟逻辑

替换原直接await Task.Delay的逻辑,每次延迟创建和服务停止令牌联动的取消源,处理取消异常:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        _logger.LogInformation(Logs.InformationWorkerRunning, _workerName, DateTime.UtcNow);

        try
        {
            using var scope = _serviceProvider.CreateScope();
            await DoWork(scope.ServiceProvider);
        }
        catch (Exception e)
        {
            _logger.LogCritical(e, e.Message);
        }
        finally
        {
            try
            {
                // 创建和服务停止令牌联动的取消源,保证服务关闭时延迟能正常响应退出
                using var delayCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
                _delayCts = delayCts;
                await Task.Delay(Interval, delayCts.Token);
            }
            catch (OperationCanceledException)
            {
                // 两种触发场景:
                // 1. 服务停止:外层while判断stoppingToken状态后直接退出循环
                // 2. 配置更新触发延迟取消:外层while继续执行,下一次延迟自动使用新间隔
            }
            finally
            {
                _delayCts = null;
            }
        }
    }
}

完整修改后代码

public abstract class WorkerBase<TWorker, TWorkerOptions> : BackgroundService
    where TWorker : WorkerBase<TWorker, TWorkerOptions>
    where TWorkerOptions : IHaveIntervalProperty
{
    private readonly ILogger<TWorker> _logger;
    private readonly IServiceProvider _serviceProvider;
    private readonly string _workerName;
    private CancellationTokenSource? _delayCts;

    protected WorkerBase(IServiceProvider serviceProvider, ILogger<TWorker> logger, IOptionsMonitor<TWorkerOptions> options)
    {
        _logger = logger;
        _serviceProvider = serviceProvider;
        _workerName = typeof(TWorker).Name;
        Interval = options.CurrentValue.Interval;
        options.OnChange(UpdateOptions);
    }

    public TimeSpan Interval { get; private set; }

    public virtual void UpdateOptions(TWorkerOptions options)
    {
        Interval = options.Interval;
        _delayCts?.Cancel();
    }

    public abstract Task DoWork(IServiceProvider provider);

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            _logger.LogInformation(Logs.InformationWorkerRunning, _workerName, DateTime.UtcNow);

            try
            {
                using var scope = _serviceProvider.CreateScope();
                await DoWork(scope.ServiceProvider);
            }
            catch (Exception e)
            {
                _logger.LogCritical(e, e.Message);
            }
            finally
            {
                try
                {
                    using var delayCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken);
                    _delayCts = delayCts;
                    await Task.Delay(Interval, delayCts.Token);
                }
                catch (OperationCanceledException)
                {
                    // 忽略取消异常,由外层逻辑控制后续走向
                }
                finally
                {
                    _delayCts = null;
                }
            }
        }
    }
}

效果说明

  • 若配置变更时任务正处于DoWork执行阶段:不会打断当前任务执行,等任务完成后下一次延迟自动使用新间隔
  • 若配置变更时任务正处于延迟等待阶段:立刻取消当前延迟,进入下一轮循环,应用新间隔执行等待
  • 服务正常停止时,延迟任务会响应停止令牌正常退出,不会阻塞服务关闭流程

内容的提问来源于stack exchange,提问作者Arthur Edgarov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.13 08:39:00