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

如何使用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();
    }
}

解答

你最初的实现思路主要存在三处可以调整的点:

  1. DoSomeWork不要使用async void声明,异步方法必须返回Task,否则异常无法被上层正常捕获,严重时会直接导致进程崩溃,修改为public async Task DoSomeWork(CancellationToken cancellationToken)即可。
  2. 取消令牌不需要注入到容器中,BackgroundService的ExecuteAsync方法会默认传入和主机生命周期绑定的取消令牌,你可以直接将这个令牌传递到调度层,也可以像最终方案那样在调度器内部维护CancellationTokenSource,在StopAsync触发时执行取消,后者灵活度更高,还能单独控制调度任务的启停状态。
  3. 你给出的最终方案已经解决了最初的问题:通过调度器内部维护的CancellationTokenSource,每次触发任务执行时将关联令牌传入Execute方法,对应的Worker方法就能拿到取消令牌响应停止信号,同时Simple Injector的注册逻辑符合规范,HostedService通过AddHostedService方法绑定到了通用主机生命周期,不需要额外处理令牌注入逻辑。

另外还有两个可以优化的细节:

  • 你在LoadWorkers方法中直接从容器获取Worker实例,如果Worker是作用域服务,这么操作会让实例生命周期被拉长到和HostedService一致,建议每次执行任务时再开启作用域获取Worker实例,避免生命周期不匹配的问题。
  • 调度器中的_tasks列表操作建议加锁,避免多线程并发修改引发的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 15:45:00