Hangfire RecurringJob 与时钟同步

Hangfire RecurringJob synchronized with the clock

我正在尝试将 Hangfire 重复作业与时钟同步。基本上,我希望从下一个小时开始,然后每个小时都有一份重复性工作。

示例:如果当前时间是 9:04 下午,则重复作业应如下所示: 10:00 下午 -> 11:00 下午,00:00 上午,01:00 上午,02:00 上午等

这与我之前的问题类似:C# Timer ticking on each rounded hour (literally),但使用的是 Hangfire。

在下面的代码中,我尝试首先通过安排一个 BackgroundJob 来安排下一个小时之前的剩余分钟数,然后在执行 BackgroundJob 之后的每个小时安排 RecurringJob .问题是它会随机计时。

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

在这行代码中,您总是增加 60 分钟。

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

这是因为 Minute 属性 只能在值 0 到 59 之间,因此减少为:

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

试试这个:

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

例如,09:00 将显示为 09:00,但 09:0109:59 将四舍五入为 10:00。听起来这就是您要求的行为。

您可能还想阅读 in the HangFire docs 关于 SchedulePollingInterval 的内容,这可能会影响您的结果。

使用 cron 表达式有更好的方法。如果您打开 https://crontab.guru 并尝试一些表达式,您会找到每个表达式的解释。

一些 cron 表达式的例子:

  • 每 5 分钟:0/5 * * * *

如果当前时间是 11:12 下午,它将在 11:15 下午执行一个方法,然后在 11:20 下午等。

  • 每小时 0 * * * *

如果当前时间是11:12下午,它将在00:00上午、01:00上午、02:00上午等执行一个方法

除了cron 表达式之外,还有内置的cron 表达式。他们的源代码在这里:https://github.com/HangfireIO/Hangfire/blob/9cd09f38fa97e4c2dd48f6097985fd2b48b4568e/src/Hangfire.Core/Cron.cs#L231。如果我想每 1 小时做一些动作,那就是 Cron.Hourly().

示例代码如下:

RecurringJobManager manager = new RecurringJobManager();
manager.RemoveIfExists("myjob");

// Each 5 minutes, e.g. 01:05 pm, 01:10 pm, 01:15 pm, etc.
//manager.AddOrUpdate("myjob", Job.FromExpression(() => DoWork()), $"0/5 * * * *", TimeZoneInfo.Local);

// Each 1 hour, e.g. 01:00 pm, 02:00, 03:00 pm, etc.
manager.AddOrUpdate("myjob", Job.FromExpression(() => DoWork()), $"0 * * * *", TimeZoneInfo.Local);