如何使用Simple Injector在HostedService定时器中注入CancellationToken
基于Simple Injector实现带取消令牌的HostedService定时任务问题
我正在尝试创建一个基于Simple Injector运行HostedService的控制台应用,已参考通用主机集成文档的示例。
现在我希望将IProcessor.DoSomeWork修改为携带CancellationToken参数的async方法,使DoSomeWork支持取消操作:
public async void DoSomeWork(CancellationToken cancellationToken) { await Task.Delay(_settings.Delay, cancellationToken); }
请问我该如何从HostedService中注入该取消令牌:
private void DoWork() { try { using (AsyncScopedLifestyle.BeginScope(this.container)) { var service = this.container.GetInstance<TService>(); this.settings.Action(service); } } catch (Exception ex) { this.logger.LogError(ex, ex.Message); } }
同时该如何正确配置容器:
container.RegisterInstance(new TimedHostedService<IProcessor>.Settings( interval: TimeSpan.FromSeconds(10), action: processor => processor.DoSomeWork()));
我目前卡在这个问题上,是不是我的实现思路存在问题?
*** 更新 ***
以下是我最终采用的极简实现方案:
Program入口代码
class Program { public static async Task Main(string[] args) { var container = new Container(); IHost host = CreateHostBuilder(args, container) .Build() .UseSimpleInjector(container); ConfigureContainer(container); await host.RunAsync(); } private static void ConfigureContainer(Container container) { container.Register<IWorkScheduler, WorkScheduler>(Lifestyle.Singleton); // 设置调度定时器间隔 container.RegisterInstance(new WorkSchedulerSettings(TimeSpan.FromSeconds(1))); container.Register<DoSomethingWorker>(); container.RegisterInstance(new DoSomethingSettings(new TimeSpan(0, 0, 5))); container.Register<DoSomethingElseWorker>(); container.RegisterInstance(new DoSomethingElseSettings(new TimeSpan(0, 0, 10))); container.Verify(); } public static IHostBuilder CreateHostBuilder(string[] args, Container container) => Host.CreateDefaultBuilder(args) .ConfigureServices((hostContext, services) => { services.AddLogging(); services.AddSimpleInjector(container, options => { // 将hosted service以单例模式注册到Simple Injector中,并绑定到.NET Core通用主机管道 options.AddHostedService<BackgroundHostedService>(); services.AddLogging(); }); }) .UseConsoleLifetime(); }
HostedService实现
public class BackgroundHostedService : BackgroundService { private readonly IWorkScheduler _scheduler; private readonly Container _container; private readonly ILogger _logger; public BackgroundHostedService(IWorkScheduler scheduler, Container container, ILogger<BackgroundHostedService> logger) { _scheduler = scheduler ?? throw new ArgumentNullException(nameof(scheduler)); _container = container ?? throw new ArgumentNullException(nameof(container)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } public override Task StartAsync(CancellationToken cancellationToken) { LoadWorkers(); return base.StartAsync(cancellationToken); } protected override Task ExecuteAsync(CancellationToken cancellationToken) { try { _scheduler.Start(); } catch (Exception ex) { _logger.LogError(ex, ex.Message); } return Task.CompletedTask; } public override async Task StopAsync(CancellationToken cancellationToken) { await _scheduler.Stop(); } private void LoadWorkers() { // 绑定触发器,指定触发周期以及是否需要单时间点单次运行 WorkTrigger trigger1 = new WorkTrigger(_container.GetInstance<DoSomethingWorker>(), new TimeSpan(0, 0, 2), false); _scheduler.AddTrigger(trigger1); WorkTrigger trigger2 = new WorkTrigger(_container.GetInstance<DoSomethingElseWorker>(), new TimeSpan(0, 0, 5), true); _scheduler.AddTrigger(trigger2); } public override void Dispose() { _scheduler.Dispose(); base.Dispose(); } }
调度器实现
public interface IWorkScheduler : IDisposable { void Start(); Task Stop(); void AddTrigger(WorkTrigger trigger); } public class WorkSchedulerSettings { public readonly TimeSpan Interval; public WorkSchedulerSettings(TimeSpan interval) { Interval = interval; } } public class WorkScheduler : IWorkScheduler, IDisposable { private readonly Timer _timer; private readonly WorkSchedulerSettings _settings; private readonly ILogger<WorkScheduler> _logger; private readonly List<Task> _tasks; private readonly List<WorkTrigger> _triggers; private readonly CancellationTokenSource _cancTokenSource; public WorkScheduler(WorkSchedulerSettings settings, ILogger<WorkScheduler> logger) { _settings = settings ?? throw new ArgumentNullException(nameof(settings)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _timer = new Timer(callback: _ => DoWork()); _tasks = new List<Task>(); _triggers = new List<WorkTrigger>(); _cancTokenSource = new CancellationTokenSource(); } public void Start() { _logger.LogInformation("Scheduler started"); _timer.Change(dueTime: TimeSpan.Zero, period: _settings.Interval); } public async Task Stop() { _timer.Change(Timeout.Infinite, Timeout.Infinite); _cancTokenSource.Cancel(); await Task.WhenAll(_tasks); _tasks.Clear(); _logger.LogInformation("Scheduler stopped"); } public void AddTrigger(WorkTrigger trigger) { if (trigger == null) throw new ArgumentNullException(nameof(trigger)); _triggers.Add(trigger); } private void DoWork() { foreach (var trigger in _triggers) { if (trigger.CanExecute(DateTime.Now)) { var task = trigger .Execute(_cancTokenSource.Token) .ContinueWith(x => HandleError(x)); _tasks.Add(task); } } _tasks.RemoveAll(x => x.IsCompleted); } private void HandleError(Task task) { if (task.IsFaulted) _logger.LogError(task.Exception.Message); } public void Dispose() { _timer?.Dispose(); _cancTokenSource?.Dispose(); } }
解答
你最初的实现思路主要存在三处可以调整的点:
DoSomeWork不要使用async void声明,异步方法必须返回Task,否则异常无法被上层正常捕获,严重时会直接导致进程崩溃,修改为public async Task DoSomeWork(CancellationToken cancellationToken)即可。- 取消令牌不需要注入到容器中,
BackgroundService的ExecuteAsync方法会默认传入和主机生命周期绑定的取消令牌,你可以直接将这个令牌传递到调度层,也可以像最终方案那样在调度器内部维护CancellationTokenSource,在StopAsync触发时执行取消,后者灵活度更高,还能单独控制调度任务的启停状态。 - 你给出的最终方案已经解决了最初的问题:通过调度器内部维护的
CancellationTokenSource,每次触发任务执行时将关联令牌传入Execute方法,对应的Worker方法就能拿到取消令牌响应停止信号,同时Simple Injector的注册逻辑符合规范,HostedService通过AddHostedService方法绑定到了通用主机生命周期,不需要额外处理令牌注入逻辑。
另外还有两个可以优化的细节:
- 你在
LoadWorkers方法中直接从容器获取Worker实例,如果Worker是作用域服务,这么操作会让实例生命周期被拉长到和HostedService一致,建议每次执行任务时再开启作用域获取Worker实例,避免生命周期不匹配的问题。 - 调度器中的
_tasks列表操作建议加锁,避免多线程并发修改引发的异常。
内容的提问来源于stack exchange,提问作者Paolo
相关产品推荐
相关产品推荐

