Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

I am working on a project based on ASP.NET Core 3.1 and I want to add a specific functionality to it to schedule publishing a post in the future in a date and time specified by post author (something like what Wordpress does for scheduled posts through its cron jobs). For example, if we receive this date and time from user :

2020-09-07 14:08:07

Then, how can I schedule a background task for it by using hosted services to run only for one time and to change a flag in database and save changes after that?

I've read some articles about it but they didn't specify date and time and just mentioned repeated tasks for every 5 second and stuff like that with cron expressions, but, the thing I need to know is how can I schedule a background task for a specific date and time?

Thank you in advance.

like image 253
Aspian Avatar asked Sep 08 '20 13:09

Aspian


People also ask

How do I schedule a job in .NET core?

We can use Quartz scheduling to perform a job every 5 minutes. To begin, make a project with the ASP.NET core web application template. Choose Asp.net MVC for your online application. There are two ways to install the Quartz package.

What is background service in net core?

BackgroundService is a base class for implementing a long running IHostedService. ExecuteAsync(CancellationToken) is called to run the background service. The implementation returns a Task that represents the entire lifetime of the background service.


2 Answers

After some trial and error I found a way to schedule a background task for specific date and time by using hosted service as I asked in the question, and, I did that with System.Threading.Timer and Timespan like this:

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

If you look at the part of the above code in which I passed (item.ScheduledFor - DateTime.UtcNow) as Timer class constructor's third parameter to initialize a new instance of it, I actually ask the timer to do a specific work in a specific time I stored as a DateTime in item.ScheduledFor.

You could read more about background tasks with hosted services in ASP.NET Core here from official Microsoft docs:

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

To see the full implementation in my Github repo which has the possibility to recover the scheduled tasks from database after restarting the server, use the following link:

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

like image 97
Aspian Avatar answered Oct 20 '22 05:10

Aspian


I combined CrontabSchedule with IHostedService. The implementation below is lightweight (no architecture imposing libs) and no polling.

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;
}
like image 26
Paulius Raila Avatar answered Oct 20 '22 05:10

Paulius Raila