IHostedService/BackgroundService to run on a schedule (as opposed to Task.Delay)

我是研究僧i 提交于 2020-01-14 19:17:07

问题


Microsoft's example for a forever/continous IHostedService at Implement background tasks in microservices with IHostedService and the BackgroundService class uses while+Task.Delay 'pattern'. This illustrated with a code snippet that a simplified version is just below.

public class GracePeriodManagerService : BackgroundService

(...) 

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

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

This pattern suffers from a creeping shift - the work is done every timeSpan+how_long_work_took. Even when how_long_work_took is very small over a period of time it adds up.

I would like to avoid calculating timeSpan based on how long work took.

What would a robust solution be to run every fixed_amount_of_time?.

Thinking out loud: If I use a task scheduler library, like HangFire, inside ExecuteAsync does using IHostedService/BackgroundService even make sense any more?

A bonus would be to be able to run a task at a point in time (e.g. at midnight)


回答1:


This is how I handle such thing... In my case I need to start the service on specific day, specific hour and repeat every x days. But I don't know if it's what are you looking for exactly :)

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);
        }
    }
}

I wrote Delay function to handle delays longer that 28 days.




回答2:


You could consider using the Reactive extenstions for .NET and implement as Observables with a Timer and a Cancellation Token. With a Scheduler you can determine the best approach threading approach (refer here)

The code snippet below could be used in the ExecuteAsync method which shows an arbitrary 3 second startup time and then having a due date of 60 seconds (could be any time length. Note the Timestamp() which allows passing of the local time with the integer.

CancellationToken cancellationToken = CancellationToken.None;

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


来源:https://stackoverflow.com/questions/51667000/ihostedservice-backgroundservice-to-run-on-a-schedule-as-opposed-to-task-delay

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!