【问题标题】:In ASP.NET Core 3.1, how can I schedule a background task (Cron Jobs) with hosted services for a specific date and time in the future?在 ASP.NET Core 3.1 中,如何在未来的特定日期和时间安排具有托管服务的后台任务(Cron 作业)?
【发布时间】:2020-09-08 13:49:16
【问题描述】:

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

2020-09-07 14:08:07

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

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

提前谢谢你。

【问题讨论】:

标签: c# asp.net-core scheduled-tasks asp.net-core-hosted-services


【解决方案1】:

我将 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;
}

【讨论】:

  • 对您的代码稍加解释可能会大有帮助...
  • 这里解释了几乎相同的方法:medium.com/@gtaposh/…
  • @Dejan 确实,我读过它。但我选择不投票,而是等待。
【解决方案2】:

经过反复试验,我找到了一种方法,可以使用托管服务在特定日期和时间安排一个后台任务,正如我在问题中所问的那样,我使用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();
   }
}

如果你看上面代码中我传递(item.ScheduledFor - DateTime.UtcNow)作为定时器类构造函数的第三个参数来初始化它的新实例的部分,我实际上是要求定时器在我存储的特定时间做特定的工作作为item.ScheduledFor 中的日期时间。

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

https://docs.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/Infrastructure/Schedule

【讨论】:

  • 但是重启应用程序后它会丢弃所有计时器,对吧?
  • 是的,但是在“ScheduledItemChangeState”方法中,您可以将所有任务存储在数据库表中,并且在重新启动后,它会检查是否有运行任何任务的时间来运行它们,并在运行后删除他们从桌子上。所以无论如何您都不会丢失计划任务。
  • @Mateech 您可以通过以下链接在我的 Github 存储库中看到完整的实现,该实现有可能在重新启动服务器后从数据库中恢复计划任务:github.com/aspian-io/aspian/tree/master/Infrastructure/Schedule
【解决方案3】:

我想为其添加特定功能,以安排将来在帖子作者指定的日期和时间发布帖子。例如,如果我们从用户收到此日期和时间:2020-09-07 14: 08:07 .

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

您似乎想在用户指定的日期时间执行后台任务/作业,为了达到要求,您可以尝试使用一些消息队列服务,例如Azure Queue Storage,这使我们能够指定如何通过设置visibilityTimeout,消息应该对 Dequeue 和 Peek 操作不可见。

当您的应用程序用户想要创建新帖子并指定发布日期时间时,您可以将新消息(具有基于用户预期日期时间的指定可见性超时)插入队列,以便这条新插入的消息仅可见在队列中的指定日期时间。

QueueClient theQueue = new QueueClient(connectionString, "mystoragequeue");

if (null != await theQueue.CreateIfNotExistsAsync())
{
    //The queue was created...
}

var newPost = "Post Content Here";

var user_specified_datetime = new DateTime(2020, 9, 9, 20, 50, 25);
var datetime_now = DateTime.Now;

TimeSpan duration = user_specified_datetime.Subtract(datetime_now);

await theQueue.SendMessageAsync(newPost, duration, default); 

然后您可以实现一个队列触发的后台任务来从队列中检索消息并更新您的数据库记录。

注意:Microsoft Azure Storage Emulator 是一个模拟 Azure Queue 等服务用于本地开发和测试目的的工具,您可以尝试在本地针对存储服务测试代码,而无需创建 Azure 订阅或产生任何费用。

【讨论】:

  • 感谢您非常彻底的回答,实际上如果我使用消息队列服务会很棒。但是由于某种原因,我仍然必须为此使用 System.Threading.Timer 之类的东西,并且使用 Timespan 我想我找到了一种方法来做到这一点。非常感谢您的回答和描述。
【解决方案4】:

使用DNTScheduler 并设置具体的日期和时间

        services.AddDNTScheduler(options =>
        {
            // DNTScheduler needs a ping service to keep it alive. Set it to false if you don't need it. Its default value is true.
            // options.AddPingTask = false;

            options.AddScheduledTask<DoBackupTask>(
                runAt: utcNow =>
                {
                    var now = utcNow.AddHours(3.5);
                    return  now.Hour == 14 && now.Minute == 08 && now.Second == 07;
                },
                order: 1);
        });

【讨论】:

  • 呃。这看起来很棒,但被弃用了...... DNTCommon.Web.Core,它可以完成所有类型的事情我不需要,有很多依赖项。
猜你喜欢
  • 1970-01-01
  • 2018-12-02
  • 2021-02-06
  • 2016-07-30
  • 1970-01-01
  • 2010-11-11
  • 2022-07-11
  • 1970-01-01
  • 2018-01-31
相关资源
最近更新 更多