运行 ASP.NET 核心中每 5 分钟一次的异步托管服务

Run async hosted service every 5 minutes in ASP.NET Core

后台服务的 ASP.NET 核心 docs 展示了一些实施示例。

有一个在 timer, though it's synchronous. There's another example which is asynchronous, for starting a service with a scoped dependency 上启动服务的示例。

我需要同时执行这两项操作:每 5 分钟启动一次服务,并且它具有范围内的依赖项。没有这方面的例子。

我结合了这两个示例,但我不确定将 Timer 与异步 TimerCallback.

一起使用的安全方法

例如

public class MyScheduler : IHostedService
{
  private Timer? _timer;
  private readonly IServiceScopeFactory _serviceScopeFactory;

  public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;

  public void Dispose() => _timer?.Dispose();

  public Task StartAsync(CancellationToken cancellationToken)
  {
    _timer = new Timer((object? state) => {
      using var scope = _serviceScopeFactory.CreateScope();
      var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
      await myService.Execute(cancellationToken);            // <------ problem
    }), null, TimeSpan.Zero, TimeSpan.FromSeconds(5));

    return Task.CompletedTask;
  }

  public Task StopAsync(CancellationToken cancellationToken) {
    _timer?.Change(Timeout.Infinite, 0);
    return Task.CompletedTask;
  }

}

计时器需要一个同步回调,所以问题出在await。调用异步服务的安全方法是什么?

使用异步处理程序创建一个事件并每隔一段时间引发一次。可以等待事件处理程序

public class MyScheduler : IHostedService {
    private Timer? _timer;
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;

    public void Dispose() => _timer?.Dispose();
    
    public Task StopAsync(CancellationToken cancellationToken) {
        _timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    }
    
    public Task StartAsync(CancellationToken cancellationToken) {
        performAction += onPerformAction; //subscribe to event
        ScopedServiceArgs args = new ScopedServiceArgs {
            ServiceScopeFactory = _serviceScopeFactory,
            CancellationToken = cancellationToken
        };
        _timer = new Timer((object? state) =>  performAction(this, args), //<-- raise event
            null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
        return Task.CompletedTask;
    }
  
    private event EventHandler<ScopedServiceArgs> performAction = delegate { };
    
    private async void onPerformAction(object sender, CancellationArgs args) {
        using IServiceScope scope = args.ServiceScopeFactory.CreateScope();
        IMyService myService = scope.ServiceProvider.GetRequiredService<IMyService>();
        await myService.Execute(args.CancellationToken);
    }
    
    class ScopedServiceArgs : EventArgs {
        public IServiceScopeFactory ServiceScopeFactory {get; set;}
        public CancellationToken CancellationToken {get; set;}
    }

}

使用BackgroundService代替IHostedService

public class MyScheduler : BackgroundService
{
    private readonly IServiceScopeFactory _serviceScopeFactory;
    public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // Option 1
        while (!stoppingToken.IsCancellationRequested)
        {
            // do async work
            using (var scope = _serviceScopeFactory.CreateScope())
            {
              var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
              await myService.Execute(stoppingToken);
            }
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }

        // Option 2 (.NET 6)
        var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            // do async work
            // ...as above
        }
    }
}