Search code examples
c#cronhangfire

Hangfire RecurringJob synchronized with the clock


I'm trying to synchronize a Hangfire recurring job with the clock. Basically, I want to have a recurring job starting by the next hour and then on each hour.

Example: if the current time is 9:04 PM, the recurring job should be as following: 10:00 PM -> 11:00 PM, 00:00 AM, 01:00 AM, 02:00 AM, etc.

It is similar to my previous question: C# Timer ticking on each rounded hour (literally), but using Hangfire.

In the code below, I tried to make it first by scheduling a BackgroundJob for the minutes left until the next hour and then RecurringJob for each hour after BackgroundJob was executed. The problem is that it ticks at random times.

private DateTime RoundCurrentToNextOneHour()
{
    DateTime now = DateTime.Now, result = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0);
    return result.AddMinutes(((now.Minute / 60) + 1) * 60);
}

public Task StartAsync(CancellationToken cancellationToken)
{
    _logger.LogInformation("Timed Background Service is starting.");

    BackgroundJob.Schedule(() => StartRecurringJob(), RoundCurrentToNextOneHour());

    return Task.CompletedTask;
}

public void StartRecurringJob()
{
    RecurringJob.AddOrUpdate(() => DoWork(), Cron.Hourly, TimeZoneInfo.Local);
}

Solution

  • In this line of code, you are always adding 60 minutes.

    result.AddMinutes(((now.Minute / 60) + 1) * 60)
    

    This is because the Minute property can only be in the values 0 through 59, and thus reduces as:

    result.AddMinutes((0 + 1) * 60)
    result.AddMinutes(1 * 60)
    result.AddMinutes(60)
    

    Try this instead:

    result.AddMinutes((60 - (now.Minute % 60)) % 60)
    

    For example, 09:00 will come out as 09:00, but 09:01 through 09:59 will round up to 10:00. It sounds like this was the behavior you were asking for.

    You might also want to read in the HangFire docs about the SchedulePollingInterval, which could affect your results.