Search code examples
c#.net-5background-service

BackgroundService runs on unscheduled times


I have this BackgroundService:

public class ReminderService : BackgroundService
{

    private readonly int[] reminderDays = { 1, 3, 6, 9 };

    protected override async Task ExecuteAsync(CancellationToken stopToken)
    {
        while (!stopToken.IsCancellationRequested)
        {
            DateTime now = DateTime.Now;
            DateTime start = now.Date.AddHours(13);
            if (now > start) start = start.AddDays(1);
            await Task.Delay(start.Subtract(now), stopToken);
            if (reminderDays.Contains(now.Day)) await DoWork();
        }
    }

    protected async Task DoWork()
    {
        // The work...
    }

}

I want the service to run the first, third, sixth and ninth day each month at 1 PM. From what I can see in my log, the service rarely runs a few milliseconds past the expected time but often a day too late (2, 4, 7, 10 at around 1 PM).

If can almost only be in my Task.Delay calculation, but I don't know why.

I have other services running with different starts/delays but with the same problem.

Can anyone see what's wrong? I'm open for other approaches but I prefer not to use libraries like Hangfire.


Solution

  • You might look for a scheduler quartz, which is very suitable to use the crontab format that is very suitable for the background scheduler.

    If you want to find a light way crontab library Cronos or NCrontab that can only get the time by crontab.

    For all Of them, I would prefer using Cronos library because that supports UTC and daylight saving time transitions.

    public class ReminderService : BackgroundService
    {
    
        private const string cronExpression= "0 13 1,3,6,9 * *"; 
        private readonly CronExpression _cronJob;
        public ReminderService()
        {
          _cronJob = CronExpression.Parse(cronExpression);
        }
    
        protected override async Task ExecuteAsync(CancellationToken stopToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
              var now = DateTime.UtcNow;
              var nextUtc = _cronJob.GetNextOccurrence(now);
              await Task.Delay(now - nextUtc.Value, stoppingToken);
              await DoWork();
            }
        }
    
        protected async Task DoWork()
        {
            // The work...
        }
    }
    

    Here is a very awesome website cron-expression-generator helps us get crontab expression by UI