Guide | Kentico

Kentico Scheduled Tasks: Tips & Tricks

Senior developer Lee Conlin shares his key pro tips for harnessing Kentico's scheduled tasks.

9 February 2018 ( words)
Lee Conlin Lee Conlin

Almost every site I develop in Kentico requires some form of scheduled task.

These often form the backbone of integrations with other systems but are often used for clean-up tasks as well. In this article, I’m going to show you a few of the tricks I use with Kentico’s Scheduled Tasks that make my life easier and the tasks more useful.

1. Tasks that reschedule themselves

Sometimes I want a task that follows an odd schedule. Maybe I want it to run every minute between 2am and 4am and then every 30 minutes the rest of the time. Maybe I just want the task its self to control its schedule. Whatever the reason, having tasks reschedule themselves makes a lot of sense.

using System;

using CMS.Scheduler;

public class MyCustomTask : ITask
{
    public string Execute(TaskInfo task)
    {
        // Do your code here

        // Set any new task data you need into task.TaskData

        // Set the next run time you want
        DateTime nextRun = DateTime.Now.AddMinutes(1);
        RescheduleSelf(task, nextRun);

        return $"Rescheduled to run again at {nextRun.ToString("yyyy-MM-dd HH:mm:ss")}";
    }

    private void RescheduleSelf(TaskInfo task, DateTime nextRun)
    {
        task.TaskInterval = SchedulingHelper.EncodeInterval(new TaskInterval
        {
            Period = "once",
            UseSpecificTime = true,
            StartTime = nextRun
        });
        task.Update();
    }
}

This method is useful for cleanup tasks that need to delete records from tables that grow quickly or for operations that might cause performance degradation on the site. It allows you to run those performance-impacting tasks more frequently outside of the peak hours for the site which keeps the impact to a minimum.

2. Using structured task data

When working with scheduled tasks its often helpful to pass in data for the task to use during its execution.

This is particularly helpful if the tasks are created dynamically at runtime or to pass data from one execution of a task to the next (i.e. when using tip #1 above)

The simplest way to do this in code is with the Newtonsoft.Json package to serialize/deserialize the data to/from a string.

To do this effectively you must create a custom POCO class to hold your task data.

public class MyCustomTaskData{
    public DateTime LastRunTime{ get;set; }
    public int? NextTimeIntervalMinutes{ get;set; }
}

Then in the task, you can deserialize this data easily.

using System;
using CMS.Scheduler;
using Newtonsoft.Json;

public class MyCustomTask : ITask
{
    public string Execute(TaskInfo task)
    {
        var taskData = JsonConvert.DeserializeObject<MyCustomTaskData>(task.TaskData);

        // Do your code here

        // Set the next run time you want
        // assume here that taskData.NextTimeIntervalMinutes = null on first run
        DateTime nextRun = DateTime.Now.AddMinutes(taskData.NextTimeIntervalMinutes ?? 30);

        // Set any new task data you need into task.TaskData
        taskData.LastRunTime = task.TaskLastRunTime;
        taskData.NextTimeIntervalMinutes = (DateTime.Now.Hour > 2 && DateTime.Now.Hour < 4) ? 1 : 30;

        // Put the task data back into the task
        task.TaskData = JsonConvert.SerializeObject(taskData);

        RescheduleSelf(task, nextRun);

        return $"Rescheduled to run again at {nextRun.ToString("yyyy-MM-dd HH:mm:ss")}";
    }

    private void RescheduleSelf(TaskInfo task, DateTime nextRun)
    {
        task.TaskInterval = SchedulingHelper.EncodeInterval(new TaskInterval
        {
            Period = "once",
            UseSpecificTime = true,
            StartTime = nextRun
        });
        task.Update();
    }
}

Using structured data in this way to pass information between executions of a task is a no-brainer in my opinion. It makes the retrieval of that data much simpler and avoids the need to write your own parsing code.

3. Using "run once" tasks to carry out jobs only when needed

When you create a class that implements ITask there is no reason that you have to install it as a scheduled task in the Kentico UI.

Instead, you could have some other code elsewhere in your solution create the task and then have the task delete its self once it has finished.

You could couple this with the tips in #1 and #2 above to have a task that runs multiple times, perhaps to process a large amount of data in batches, and only delete its self once it has completely finished.

Within any other code in your project you could do the following to create a new instance of a task:

var taskData = new MyCustomTaskData();
var task = new TaskInfo
{
    TaskDisplayName = $"New instance of my custom task",
    TaskName = $"MyCustomTask_{DateTime.UtcNow:yyyyMMddhhmmss}",
    TaskAssemblyName = "CMSApp",
    TaskClass = "CMSApp.MyCustomTask",
    TaskData = JsonConvert.SerializeObject(taskData),
    TaskInterval = SchedulingHelper.EncodeInterval(new TaskInterval
    {
        Period = "once",
        UseSpecificTime = true,
        StartTime = DateTime.Now.AddMinutes(taskData.NextTimeIntervalMinutes ?? 1)
    }),
    TaskEnabled = true,
    TaskNextRunTime = DateTime.Now.AddMinutes(taskData.NextTimeIntervalMinutes ?? 1),
    TaskLastResult = "Queued"
};
task.Insert();

Then, in your task when you are happy that it has finished everything (even if that is after multiple runs using tip #1 above) you can simply delete the task:

task.Delete();

This method allows you to create very long running processes that do not time out. I use this method for creating file-based data imports where the client is providing large files to be imported into Kentico.

4. Generic tasks with multiple instances

An ITask can be written in a generic way allowing CMS users to create instances of your ITask where each instance might do something different, all driven by structured task data.

For example, you could create an ITask that will execute a Kentico Custom Query with parameters.

Your Task Data would contain the Query Name and the parameter-value pairs meaning that the code within your custom ITask doesn't need to have them hard-coded.

Such a task could be used to allow INSERT, UPDATE and DELETE queries to be executed on a schedule by simply creating a new custom query in Kentico and a new instance of the ITask and setting a schedule.

This provides the ultimate in re-usability.

Summary

Kentico CMS Scheduled Tasks are much more flexible and useful than many developers realise and can be used to provide large performance and efficiency benefits to many sites.

It's also worth noting that to get the best performance out of large or long-running scheduled tasks you should install the Scheduler Windows Service that Kentico provides and configure your task to use it.

Lee Conlin

Author: Lee Conlin