如何按小时启动我的 Worker Service

How to start my Worker Service on the hour

我有这个工人服务开始工作,但它每小时检查一次工作。我怎样才能让它按小时而不是每小时 运行 时间检查?

public class WorkerService : BackgroundService
    {
        private const int generalDelay = 20;  //minutes
        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            while (!stoppingToken.IsCancellationRequested)
            {
                await Task.Delay(generalDelay * 60000, stoppingToken);
                await DoBackupAsync();
            }
        }
        private static Task DoBackupAsync()
        {
            DoWork d = new DoWork();
            return Task.FromResult("Done");
        }
    }

主要是因为我知道作业何时会 运行 并且可以预测和安排我的更新在 运行 时间之间以及所有作业是否完成。

如果您想查看当前时间,您可以使用 var datetime = DateTime.UtcNow 这将采用当前的日期时间,然后您可以使用 datetime.Hour 获取当前小时值,以便您可以使用它来检查。

您可能会像@Paweł Łukasik 说的那样寻找一个调度程序,它非常适合使用 crontab 格式。

quartz是一个强大的库,可以很好的控制作业时间,但是quartz需要配合它的库架构

如果你的代码比较庞大,难以修改,我建议你使用一个只能通过crontab获取时间的轻型库NCrontab

0 * * * *

然后你可以用GetNextOccurrence方法计算下一个小时

public class WorkerService : BackgroundService
{
    const string EveryHours = "0 * * * *";
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        var schedule = CrontabSchedule.Parse(EveryHours);
        while (!stoppingToken.IsCancellationRequested)
        {
            var now = DateTime.Now;
            DateTime nextExecutionTime = schedule.GetNextOccurrence(now);
            await Task.Delay((nextExecutionTime - now).Milliseconds, stoppingToken);
            await DoBackupAsync();
        }
    }
    private static Task DoBackupAsync()
    {
        DoWork d = new DoWork();
        return Task.FromResult("Done");
    }
}

您可以使用下面的WaitUntilNextHourAsync方法:

public static Task WaitUntilNextHourAsync(
    CancellationToken cancellationToken = default)
{
    DateTime now = DateTime.Now;
    DateTime next = now.Date.AddHours(now.Hour + 1);
    Debug.Assert(next > now);
    TimeSpan delay = next - now;
    Debug.Assert(delay > TimeSpan.Zero && delay.TotalHours <= 1);
    if (delay.TotalSeconds < 1) delay += TimeSpan.FromHours(1);
    return Task.Delay(delay, cancellationToken);
}

用法示例:

protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
    while (true)
    {
        await WaitUntilNextHourAsync(stoppingToken);
        await DoBackupAsync();
    }
}

对于这种要求,full-blown 调度程序可能有点矫枉过正。我建议使用 cron 表达式,但我更喜欢 Cronos 而不是 NCrontab。 Cronos 在 UTC 和夏令时转换方面有更好的测试和 well-defined 行为。

这是使用 Cronos 的样子:

public class WorkerService : BackgroundService
{
  private const string schedule = "0 * * * *"; // every hour
  private readonly CronExpression _cron;

  public WorkerService()
  {
    _cron = CronExpression.Parse(schedule);
  }

  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    while (!stoppingToken.IsCancellationRequested)
    {
      var utcNow = DateTime.UtcNow;
      var nextUtc = _cron.GetNextOccurrence(utcNow);
      await Task.Delay(nextUtc.Value - utcNow, stoppingToken);
      await DoBackupAsync();
    }
  }
}

我从类似于已接受答案的内容开始 - 您可能不需要更多内容。但是,我最终将我的代码作为 github/nuget 项目共享。它比 Quartz.NET 和其他调度框架更基础(也更易于使用)。 试一试,如果有什么不能按预期工作,请通过 github 问题告诉我。

源代码库: https://github.com/thomasgalliker/NCrontab.Scheduler

nuget 下载: https://www.nuget.org/packages/NCrontab.Scheduler

尽情享受吧。