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
相关产品推荐
相关产品推荐

