IHostedService / BackgroundService按计划运行(与Task.Delay相对)

时间:2018-08-03 07:01:54

标签: c# .net .net-core scheduling

Microsoft在Implement background tasks in microservices with IHostedService and the BackgroundService class处永久/连续IHostedService的示例使用while + Task.Delay'模式'。 这用一个简化的代码片段在下面说明。

public class GracePeriodManagerService : BackgroundService

(...) 

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (!stoppingToken.IsCancellationRequested)
    {
        //Do work

        await Task.Delay(timeSpan, stoppingToken);
    }
}

此模式的变化缓慢-每timeSpan + how_long_work_took都会完成一次工作。即使how_long_work_took在一段时间内非常小,它也会加起来。

我想避免根据timeSpan花费的时间来计算work

什么是在每个 fixed_amount_of_time 时间都运行的健壮解决方案?

大声思考:如果我使用任务调度程序库,例如HangFire,则在ExecuteAsync内使用IHostedService / BackgroundService确实有意义吗?

奖励是能够在某个时间点(例如午夜)执行任务

2 个答案:

答案 0 :(得分:1)

这就是我处理此类事情的方式...就我而言,我需要在特定的日期,特定的时间启动服务,并每隔x天重复一次。但是我不知道这到底是不是你在寻找什么:)

public class ScheduleHostedService: BackgroundService
{
    private readonly ILogger<ScheduleHostedService> _logger;
    private readonly DaemonSettings _settings;

    public ScheduleHostedService(IOptions<DaemonSettings> settings, ILogger<ScheduleHostedService> logger)
    {
        _logger = logger;
        _settings = settings.Value;
    }
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        DateTime? callTime=null;
        if (_settings.StartAt.HasValue)
        {

            DateTime next = DateTime.Today;
            next = next.AddHours(_settings.StartAt.Value.Hour)
                .AddMinutes(_settings.StartAt.Value.Minute)
                .AddSeconds(_settings.StartAt.Value.Second);
            if (next < DateTime.Now)
            {
                next = next.AddDays(1);
            }

            callTime = next;
        }

        if (_settings.StartDay.HasValue)
        {
            callTime = callTime ?? DateTime.Now;
            callTime = callTime.Value.AddDays(-callTime.Value.Day).AddDays(_settings.StartDay.Value);
            if (callTime < DateTime.Now)
                callTime = callTime.Value.AddMonths(1);
        }
        if(callTime.HasValue)
            await Delay(callTime.Value - DateTime.Now, stoppingToken);
        else
        {
            callTime = DateTime.Now;
        }
        while (!stoppingToken.IsCancellationRequested)
        {
            //do smth
            var nextRun = callTime.Value.Add(_settings.RepeatEvery) - DateTime.Now;

            await Delay(nextRun, stoppingToken);
        }
    }
    static async Task Delay(TimeSpan wait, CancellationToken cancellationToken)
    {
        var maxDelay = TimeSpan.FromMilliseconds(int.MaxValue);
        while (wait > TimeSpan.Zero)
        {
            if (cancellationToken.IsCancellationRequested)
                break;
            var currentDelay = wait > maxDelay ? maxDelay : wait;
            await Task.Delay(currentDelay, cancellationToken);
            wait = wait.Subtract(currentDelay);
        }
    }
}

我编写了Delay函数来处理超过28天的延迟。

答案 1 :(得分:0)

您可以考虑使用.NET的反应式扩展并将其实现为Observables with a TimerCancellation Token。使用Scheduler,您可以确定最佳的方法线程方法(请参阅here

下面的代码段可用于ExecuteAsync方法中,该方法显示任意3秒的启动时间,然后具有60秒的到期日期(可以是任何时间长度。请注意Timestamp()允许使用整数传递本地时间。

CancellationToken cancellationToken = CancellationToken.None;

Observable
  .Timer(TimeSpan.FromSeconds(3), TimeSpan.FromSeconds(60))
  .Timestamp()
  .ObserveOn(NewThreadScheduler.Default)
  .Subscribe(  
        x =>
       {
            // do some task
       } , 
        cancellationToken);