Sitecore Job - executing a job

In my previous post, I mentioned triggering a Sitecore Job from a controller with Powershell.

A special thanks to colleague Marek Musielak for the inspiration to handle the messages.

A job is a background task that you run which leverages the Sitecore JobManager for "managing everything around it".

The Sitecore platform itself uses jobs to do a variety of tasks such as: Publishing, ListManagent or Updating of indexes. You can find information about running, queued or finished jobs in the /sitecore/admin/jobs.aspx page.

What is the JobManager about

Lets have a quick look on what the JobManager can do for you:

Jobs are identified on name which must be unique and most methods of the JobManager require a jobName as string.

Creating your custom job

Find additional information here: https://sitecore-community.github.io/docs/documentation/Sitecore Fundamentals/Asynchronous Tasks/#jobs_api

I'll show you an example implementation below. But my class will have the following things:

  • a jobname, a unique string which can contain spaces
  • a method to start the job
  • the method that runs when the job is started
public class ProcessZipJob
{
    private const string JobName = "Process Zip Job";
    public static BaseJob Job => JobManager.GetJob(JobName);

    public void StartJob(Item contextItem, string folderPath)
    {
    	var options = new DefaultJobOptions(JobName, "ContentEditor", Context.Site.Name, this, "Run", new object[] {contextItem, folderPath });
        var job = JobManager.Start(options);
        job.Options.AfterLife = new TimeSpan(0, 10, 0);
    }

    public void Run(Item contextItem, string uploadedFile)
    {
    	// Do what you need to do here.
        // Example: trigger a Sitecore Pipeline processor
    }
}

The method that is called is StartJob, which will pass DefaultJobOptions to the JobManager.Start method:

  • the JobName
  • a category, eg. "ContentEditor"
  • a SiteName
  • an object who's method will be run. Since you have an instance of the class that you are in, you can pass this
  • the MethodName which will be run on the object, in the example I used 'Run'
  • an object array for the parameters that are needed for the Run method.
Sitecore keeps the job around for a limited amount of time afterwards in case you want to check its status. This is called the “after-life”.
By default the after-life value is 1 minute.

So important, set the AfterLife to something that you are comfortable with. I went with 10 minutes: job.Options.AfterLife = new TimeSpan(0, 10, 0);

If you want to check the status of a job or check the messages that are being kept on the job after the default 1 minute; you'll have to keep the job around for longer.

It is advisable to add logging to whatever code you are executing in the run method.

Adding messages to a job

Each job has a MessageQueue which you can use to send updates to. This can be used to track the progress of your job if it is executing lots of code.

I'm using a custom message class since the object you need to pass to PutMessage, must inherit from IMessage.

public class ProcessZipMessage : IMessage
{
    public string Message { get; set; }
    public void Execute()
    {
    	// gets run when your message gets dequeued
    }
}
job.MessageQueue.PutMessage(new ProcessZipMessage { Message = message});

The queue is a bit tricky, since it requires a instance of a JobMonitor class handling these messages. If you don't have an implemtation for it, it will not handle anything and your messages will stay on the queue.
The Execute method will also not trigger.
But I went too deep into decompiling the JobMonitor class to see what it exactly does. It also inspects messages if the start with event: for example.

So... introducing a custom solution... a static method to get ALL messages from the queue and to Requeue everything as well.
When calling queue.GetMessage() it will remove your message. We readd it again so you can keep calling the GetMessages() method. This creates a bit of overhead but lets you have a full overview of all messages.

Note that if you want to leverage the Execute method from a Message, you have to call it here as well.

private static readonly object Lock = new object();
public static List<string> GetMessages()
{
    lock (Lock)
    {
        var messages = new List<ProcessZipMessage>();

        var job = ProcessZipJob.Job;

        if (job != null)
        {
            var queue = ProcessZipJob.Job.MessageQueue;

            while (queue.GetMessage(out var message))
                messages.Add((ProcessZipMessage) message);

            foreach (var importMessage in messages)
            ProcessZipJob.Job.MessageQueue.PutMessage(importMessage);
        }

        return messages.Select(m => m.Message).ToList();
    }
}

Bonus

You can also use an agent to run jobs. Check out the blog of Mark Lowe:

Make Long Running Scheduled Agents Async
Sitecore’s Scheduled Agents and Tasks have been around forever and are well-known members of the XM suite. Less known is a gotcha you might run into when implementing long running operations (i.E. background import/export, bulk updates,...) Why is that? Scheduled Agents and Tasks all run synchronous…