Mads
Mads

Reputation: 465

BackgroundService runs on unscheduled times (part 2)

In my app I have a handful of BackgroundServices. The problem is, that they run on unscheduled times. Sometimes exactly a day too late, sometimes two days in a row even though it should only run once a week.

This is how one of the BackgroundServices looks like:

using Cronos;

namespace MyApp.HostedServices
{

    public class MyFirstService : BackgroundService
    {

        private const string schedule = "0 1 0 ? * *";
        private readonly CronExpression _cron;

        public MyFirstService()
        {
            _cron = CronExpression.Parse(schedule, CronFormat.IncludeSeconds);
        }

        protected override async Task ExecuteAsync(CancellationToken stopToken)
        {
            while (!stopToken.IsCancellationRequested)
            {

                DateTime utcNow = DateTime.UtcNow;
                DateTime? nextUtc = _cron.GetNextOccurrence(utcNow);
                await Task.Delay(nextUtc.Value - utcNow, stopToken);
                if (utcNow.Day == 1) await DoWork();

                /*
                DateTime now = DateTime.Now;
                DateTime start = now.Date.AddMinutes(1);
                if (now > start) start = start.AddDays(1);
                await Task.Delay(start.Subtract(now), stopToken);
                if (now.Day == 1) await DoWork();
                */

            }
        }

        protected async Task DoWork()
        {
            // The work to be done
        }

    }

}

Another example:

using Cronos;

namespace MyApp.HostedServices
{

    public class MySecondService : BackgroundService
    {

        private const string schedule = "0 0 13 ? * *";
        private readonly CronExpression _cron;
        private readonly int[] reminderDays = { 1, 3, 6, 9 };

        public MySecondService()
        {
            _cron = CronExpression.Parse(schedule, CronFormat.IncludeSeconds);
        }

        protected override async Task ExecuteAsync(CancellationToken stopToken)
        {
            while (!stopToken.IsCancellationRequested)
            {

                DateTime utcNow = DateTime.UtcNow;
                DateTime? nextUtc = _cron.GetNextOccurrence(utcNow);
                await Task.Delay(nextUtc.Value - utcNow, stopToken);
                if (reminderDays.Contains(utcNow.Day)) await DoWork();

                /*
                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 to be done
        }

    }

}

I've asked the same question before and was adviced to use cron to calculate the next run instead of doing it myself. At first I thought that did it, but the problem persists.

For example did the MySecondService run both the sixth and seventh this month.

What could be wrong?

Upvotes: 0

Views: 647

Answers (1)

Stephen Cleary
Stephen Cleary

Reputation: 457167

I suspect your problem is here:

if (reminderDays.Contains(utcNow.Day))

utcNow is the time that the scheduled job started its loop. I.e., if it started up on Tuesday and the next trigger time was Wednesday, utcNow would refer to Tuesday, not Wednesday.

I'm pretty sure you want nextUtc there instead, which represents the actual time DoWork executes:

if (reminderDays.Contains(nextUtc.Value.Day))

Upvotes: 2

Related Questions