在 ASP.NET Core 3.1 中,如何在未来的特定日期和时间安排带有托管服务的后台任务(Cron 作业)?

Asp*_*ian 10 c# scheduled-tasks asp.net-core asp.net-core-hosted-services

我正在开发一个基于 ASP.NET Core 3.1 的项目,我想向它添加一个特定的功能,以安排将来在帖子作者指定的日期和时间发布帖子(类似于 Wordpress 通过它的 cron 作业)。例如,如果我们从用户那里收到这个日期和时间:

2020-09-07 14:08:07

那么,如何通过使用托管服务仅运行一次并更改数据库中的标志并在此之后保存更改来为其安排后台任务?

我已经阅读了一些关于它的文章,但他们没有指定日期和时间,只是提到了每 5 秒重复一次的任务以及类似 cron 表达式的内容,但是,我需要知道的是如何安排后台任务特定日期和时间?

先感谢您。

Pau*_*ila 12

我将 CrontabSchedule 与 IHostedService 结合起来。下面的实现是轻量级的(没有强加库的架构)并且没有轮询。

public class SomeScheduledService: IHostedService
{
    private readonly CrontabSchedule _crontabSchedule;
    private DateTime _nextRun;
    private const string Schedule = "0 0 1 * * *"; // run day at 1 am
    private readonly SomeTask _task;

    public SomeScheduledService(SomeTask task)
    {
        _task = Task;
        _crontabSchedule = CrontabSchedule.Parse(Schedule, new CrontabSchedule.ParseOptions{IncludingSeconds = true});
        _nextRun = _crontabSchedule.GetNextOccurrence(DateTime.Now);
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        Task.Run(async () =>
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await Task.Delay(UntilNextExecution(), cancellationToken); // wait until next time

                await _task.Execute(); //execute some task

                _nextRun = _crontabSchedule.GetNextOccurrence(DateTime.Now);
            }
        }, cancellationToken);

        return Task.CompletedTask;
    }

    private int UntilNextExecution() => Math.Max(0, (int)_nextRun.Subtract(DateTime.Now).TotalMilliseconds);

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}
Run Code Online (Sandbox Code Playgroud)

  • 对你的代码的一点解释可以有很长的路要走...... (2认同)
  • 这里解释了几乎相同的方法:https://medium.com/@gtaposh/net-core-3-1-cron-jobs-background-service-e3026047b26d (2认同)

Asp*_*ian 11

经过一番尝试和错误后,我找到了一种方法,按照我在问题中的要求使用托管服务来安排特定日期和时间的后台任务System.Threading.Timer,并且,我这样做了,Timespan如下所示:

public class ScheduleTask : IScheduler, IDisposable
{

   private Timer _timer;
   private IBackgroundTaskQueue TaskQueue { get; }

   // Set task to schedule for a specific date and time
    public async Task SetAndQueueTaskAsync(ScheduleTypeEnum scheduleType, DateTime scheduleFor, Guid scheduledItemId)
    {
        // Omitted for simplicity
        // ....

        TaskQueue.QueueBackgroundWorkItem(SetTimer);
    }

   // ......
   // lines omitted for simplicity
   // .....

   // Set timer for schedule item
   private Task SetTimer(CancellationToken stoppingToken)
   {
      // ......
      // lines omitted for simplicity
      // .....

      _timer = new Timer(DoWork, null, (item.ScheduledFor - DateTime.UtcNow).Duration(), TimeSpan.Zero);


      return Task.CompletedTask;
   }

   private void DoWork(object state)
   {
       ScheduledItemChangeState(DateTime.UtcNow).Wait();
   }

   // Changes after the scheduled time comes
   private async Task ScheduledItemChangeState(DateTime scheduleFor)
   {
       using (var scope = Services.CreateScope())
       {
           var context =
            scope.ServiceProvider
                .GetRequiredService<DataContext>();

          // Changing some data in database
       }
    }

   public void Dispose()
   {
      _timer?.Dispose();
   }
}
Run Code Online (Sandbox Code Playgroud)

如果您查看上面的代码部分,其中我(item.ScheduledFor - DateTime.UtcNow)作为 Timer 类构造函数的第三个参数传递以初始化它的新实例,我实际上要求计时器在我存储为 DateTime 的特定时间执行特定工作item.ScheduledFor

您可以从官方 Microsoft 文档中阅读有关ASP.NET Core 中托管服务的后台任务的更多信息:

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-3.1&tabs=visual-studio

要查看我的 Github 存储库中的完整实现(可以在重新启动服务器后从数据库恢复计划任务),请使用以下链接:

https://github.com/aspian-io/aspian/tree/master/Infrastruct/Schedule