【问题标题】:How to run multiple task in background service in .net core with different timer duration如何在具有不同计时器持续时间的.net核心的后台服务中运行多个任务
【发布时间】:2020-07-24 16:00:58
【问题描述】:

我正在创建一个工作服务,它将作为 Windows 服务运行。我有一个需求,我想调用两个可能有不同计时器的任务。

DoWork 应该每 5 分钟调用一次,DoAnotherWork 应该每 10 分钟左右调用一次。这两个任务可以并行运行,互不依赖。

我能够创建每 5 分钟运行一次的任务 DoWork。我对如何实现另一个具有不同计时器持续时间的任务感到有些困惑?

public class Worker : BackgroundService
{        
    private readonly IServiceScopeFactory _scopeFactory;        
    private IDataLoaderService _dataLoaderService;        

    public override Task StartAsync(CancellationToken cancellationToken)
    {
        using var scope = _scopeFactory.CreateScope();
        _dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();                        
        return base.StartAsync(cancellationToken);
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {            
        while (!stoppingToken.IsCancellationRequested)
        {
            await DoWork(stoppingToken, _dataLoaderService);
            await Task.Delay(300000, stoppingToken); //Run every 5 minutes

            await DoAnotherWork(stoppingToken, _dataLoaderService);
            await Task.Delay(600000, stoppingToken); //Run every 10 minutes
        }
    }

    private async Task DoWork(CancellationToken stoppingToken, IDataLoaderService loaderService)
    {
        await loaderService.Process();            
    }        
    
    private async Task DoAnotherWork(CancellationToken stoppingToken, IDataLoaderService loaderService)
    {
        await loaderService.Validate();            
    }
}

【问题讨论】:

  • 如果您不想使用 Hangfire 或 Quartz,您可以使用其中一个计时器(用于 example)。
  • @GuruStron 你能详细说明我如何在ExecuteAsyncStartAsync 方法中使用Timer 吗?

标签: c# .net-core asp.net-core-hosted-services


【解决方案1】:

这两个任务可以并行运行,互不依赖。

听起来你有两个服务:

public class ProcessDataLoaderWorker : BackgroundService
{
  private readonly IServiceScopeFactory _scopeFactory;

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    using var scope = _scopeFactory.CreateScope();
    var dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();

    while (true)
    {
      await dataLoaderService.Process();
      await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken); //Run every 5 minutes
    }
  }
}

public class ValidateDataLoaderWorker : BackgroundService
{
  private readonly IServiceScopeFactory _scopeFactory;

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    using var scope = _scopeFactory.CreateScope();
    var dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();

    while (true)
    {
      await dataLoaderService.Validate();
      await Task.Delay(TimeSpan.FromMinutes(10), stoppingToken); //Run every 10 minutes
    }
  }
}

我还修改了 IDataLoaderService 的使用方式,使其不会在其范围之外使用,并将 Task.Delay 参数更改为更不言自明。

【讨论】:

  • 我会在循环之前添加一个await Task.Yield(),因为第一个await之前的异常会导致启动失败。
  • @JeremyLakeman:我一般是wrap it in Task.Run for the same reason
  • @StephenCleary 这里有 2 个问题:1)这两个问题是如何“连接起来”的? 2) 为什么是while (true) 而不是while (!stoppingToken.IsCancellationRequested) .. 如果请求取消... 那么这将优雅地结束这两个后台任务?
  • @Pure.Krome: 1) 您可以使用AddHostedService 添加多个服务。 2) .NET 中取消的标准模式是抛出OperationCanceledException,所以我强烈避免IsCancellationRequested。我从某个地方认出了你的用户名...
  • @AnantJaiswal:设计选择取决于您。对于这个特定的问题,在我看来它们是不同的服务。并且可以有 10 种不同的服务。也可以让一项服务具有多个计时器(在这种情况下,您应该使用真正的计时器,而不是 Task.Delay)。
【解决方案2】:

如果您不想在您的情况下使用现有的调度库,您可以使用两个计时器,例如在这个docs 中,其中使用了System.Threading.Timer。类似的东西:

public class Worker : IHostedService, IDisposable
{        
    private readonly IServiceScopeFactory _scopeFactory;        
    private IDataLoaderService _dataLoaderService;  
    private Timer _timer1;
    private Timer _timer2;      

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _dataLoaderService = scope.ServiceProvider.GetRequiredService<IDataLoaderService>();              
        _timer1 = new Timer(DoWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(300));
        _timer2 = new Timer(DoAnotherWork, null, TimeSpan.Zero, TimeSpan.FromSeconds(600));
        return Task.CompletedTask;
    }    
   
    private async void DoWork(object _)
    {
        // or create scope and resolve here
        await _loaderService.Process();            
    }        
    
    private async void DoAnotherWork(object _)
    {
        // or create scope and resolve here
        await _loaderService.Validate();            
    }

    public Task StopAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Timed Hosted Service is stopping.");

        _timer1?.Change(Timeout.Infinite, 0);
        _timer2?.Change(Timeout.Infinite, 0);

        return Task.CompletedTask;
    }

    public void Dispose()
    {
        _timer1?.Dispose();
        _timer2?.Dispose();
    }
}

【讨论】:

  • 使用IHostedServiceBackgroundService 有什么优势?
  • @Shaggy 你不需要考虑从哪里开始计时器 =)
  • 知道了。我试过你的代码,不知道为什么,但它只一次执行。 DoWork 不是每 5 分钟调用一次。
  • BackgroundService 现在更适用了。
  • @Pure.Krome 为什么会这样?
猜你喜欢
  • 2020-11-20
  • 2014-01-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多