Job progress

Some jobs are expected to take a long time to complete and for such jobs it's nice to be able to see the progress for the job. Nexus has built-in support for jobs to report their progress through the IJobProgress interface like this:

public class ExampleJob : IScheduledJob
{
    private readonly IJobProgress _jobProgress;

    public ExampleJob(IJobProgress jobProgress)
    {
        _jobProgress = jobProgress;
    }

    public async Task<JobResults> ExecuteAsync(CancellationToken cancellationToken)
    {
        var thingsToProcess = await GetThingsToProcessAsync();

        _jobProgress.SetTotal(thingsToProcess.Count);
        foreach (var thingToProcess in thingsToProcess)
        {
            cancellationToken.ThrowIfCancellationRequested();

            await ProcessAsync(thingToProcess);

            _jobProgress.Increment(1);
        }

        return JobResults.Completed();
    }
}

The important lines here are IJobProgress.SetTotal(thingsToProcess.Count); and IJobProgress.Increment(1);. The call to IJobProgress.SetTotal() tells Nexus how much work the job expects to do and IJobProgress.Increment(1); tells Nexus that we've done some work.

The numbers to use here are up to you. If your job does multiple things and some operations take a longer time to complete than others you can increment the progress with a higher number than 1.

You can call the SetTotal() method multiple times during a job. Eg if the job sees that there's more work to do than first expected. If you increment the progress more than what the total has been set to the total will automatically increase.

Progress message

The progress can report an optional message as well. Something like this:

public class ExampleJob : IScheduledJob
{
    private readonly IJobProgress _jobProgress;

    public ExampleJob(IJobProgress jobProgress)
    {
        _jobProgress = jobProgress;
    }

    public async Task<JobResults> ExecuteAsync(CancellationToken cancellationToken)
    {
        _jobProgress.SetMessage("Calculating work to be done...");

        var thingsToProcess = await GetThingsToProcessAsync();
        var otherThingsToProcess = await GetOtherThingsToProcessAsync();

        _jobProgress.SetTotal(thingsToProcess.Count + (otherThingsToProcess.Count * 2));

        _jobProgress.SetMessage("Processing things...");
        foreach (var thingToProcess in thingsToProcess)
        {
            cancellationToken.ThrowIfCancellationRequested();

            await ProcessAsync(thingToProcess);

            _jobProgress.Increment(1);
        }

        _jobProgress.SetMessage("Processing other things...");
        foreach (var otherThingToProcess in otherThingsToProcess)
        {
            cancellationToken.ThrowIfCancellationRequested();

            await ProcessAsync(otherThingToProcess);

            _jobProgress.Increment(2);
        }

        return JobResults.Completed();
    }
}

Before we know how much work we have to do, we set a progress message which at least feedbacks that's something happening and where we are in the job execution.

In the above example we also give otherThingsToProcess a weight of 2 instead of 1 because we anticipate that they will take a longer time to process. Which gives a better indication as to when the job will be finished.

For some jobs you might not have numbers to increment but you still want to give feedback to what's happening. In such cases you can use just the progress message to indicate what the job is doing. The admin UI will display the message but won't show a progress bar.

Note that the message isn't cleared if you increment progress or change the total. If you wish to clear the current message you'll need to call IJobProgress.SetMessage(null).

Calling IJobProgress outside of a job

Most of the time you will call IJobProgress directly from inside the job implementation but there's nothing stopping you from calling it inside other services. If you call IJobProgress from a service when the service isn't invoked in a job context it will simply not doing anything. IJobProgress stores a AsyncLocal instance connected to it's current running job and if there's no context the call to IJobProgress will simply not do anything.

If you try to get a hold of IJobProgress in an assembly or application which hasn't called registered the jobs system with IServiceCollection.AddNexus().AddScheduledJobs() you'll need to inject your own mock implementation of the interface as otherwise you'll get an exception that there's no registered implementation of the interface.