Updating progress in SharePoint timer jobs
What can SharePoint tasks be used for?
Timer jobs are SharePoint mechanism that allows to centrally manage and run tasks related to SharePoint Farm’s infrastructure or Web Applications custom business logic.
Some tasks come out of the box and perform operations like Active Directory synchronization, farm health analysis, processing queued documents with Word Automation Services, etc.
Task can also be created and added programatically when you deploy a solution or activate a feature. Depending on the business scenario, they might for example:
- Send emails to users
- Generate daily/weekly reports
- Synchronize business data with other systems (database, ERP, CRM)
- Import some data from external system, eg. current exchange rates from a bank service
Monitoring task progress
Task that come out of the box almost always report their progress which can be viewed in SharePoint farm’s Central Administration (or in other ways). Figure #1 presents that view.

Fig. 1. SharePoint: monitoring progress of timer jobs. Source:
Update SharePoint Timer Job Progress Programmatically
If you want to report a progress from your own timer job, which is always instance of Microsoft.SharePoint.Administration.SPJobDefinition
class, you can simply use this.UpdateProgress(int newPercentage);
method.
Monitoring task progress might seem to be a feature of a little importance, but it can be useful to see how fast a task progresses, whether it got stuck somewhere, or when it could end if a process is time-critical. It might give some quick insight into what’s going on for developers and other people waiting for the task to finish.
But there are some problems
The method mentioned before might be enough for simple tasks, but if you need to monitor progress of some long-running timer job, you might notice some problems with it:
- Every time you want to monitor progress of a loop, you must compute a progress yourself
- and it typically involves dividing integer by other integer, which will give you incorrect result if you forget to cast to
float
- it might also result in exception if you exceed the range of 1-100 by some mistake in calculations
- you might notice performance degradation, because even if the progress (represented as integer) doesn’t change with every iteration, request is sent to SharePoint server each time you call
UpdateProgress()
- and it typically involves dividing integer by other integer, which will give you incorrect result if you forget to cast to
- You will either repeat the code like:
int newProgress = (int) (loopRangeFrom + (iterationsPassed / allIterations) * ((double)(loopRangeTo - loopRangeFrom)))
which doesn’t make the job more readable, or give up updating progress in loops and only update progress on some arbitrary “milestones” in code execution.
So even though it’s very simple, it’s a bit risky piece of code to add as there are few places to make a mistake and it might result in exceptions that may break the timer job.
Making it easier
To simplify reporting progress in loops I created a simple C# class called ProgressUpdater
that tries to get rid of all the problems listed in the previous section. Here’s the example usage of this class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class MyTimerJob : SPJobDefinition { public override void Execute(Guid contentDbId) { ProgressUpdater progress = new ProgressUpdater(this); // Initilize wrapper with Timer Job definition progress.UpdateProgress(10); // Update progress to (arbitrary) 10% progress.UpdateProgress(102); // This call will be ignored by ProgressUpdater wrapper, as progress greater than 100% is invalid List<SPListItem> itemCollecion = SomeLibrary.GetSomeItemsToProcess(); progress.StartLoop(numIterations: itemCollecion.Count, progressAfter: 90); foreach (SPListItem listItem in itemCollection) { // Process listItem somehow... progress.ProgressLoop(); // Calculate new progress after this loop's iteration, and SharePoint service if the integer value of progress changed } // now the progress is 90% } // Constructors and other methods ... } |
In this case we didn’t have to do any calculations. Instead, we need to specify when the loop starts, how many iterations it’s going to have, what progress the job should have at the end, and call ProgressLoop()
after each iteration. If that looks easier, here’s the ProgressUpdater
class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.SharePoint.Administration; namespace Taurit.SharepointHelperClasses.Jobs { /// <summary> /// Progress update helper for jobs: allows to safely update jobs progress (without risk of exceptions), /// provides easy interface to increase progress for loops with a known number of iterations /// </summary> class ProgressUpdater { /// <summary> /// Handle to a job definition. Progress is updated by executing UpdateProgress on this object. /// </summary> private SPJobDefinition jobHandle; /// <summary> /// Backing property for current progress value. Stores progress value that is always valid on server side. Use only via property. /// </summary> private int fCurrentProgress = 0; /// <summary> /// Property to wrap current progress value, so it's value is always valid on server side. /// </summary> private int currentProgress { get { return fCurrentProgress; } set { if ((value > currentProgress) && (value <= 100) && (value >= 0 )) { fCurrentProgress = value; } } } /// <summary> /// Constructor. Stores required job definition object. /// </summary> /// <param name="job"></param> public ProgressUpdater(SPJobDefinition job) { this.jobHandle = job; } /// <summary> /// Safely update progress, /// </summary> /// <param name="newValue"></param> public void UpdateProgress(int newValue) { try { var oldProgress = this.currentProgress; this.currentProgress = newValue; if (this.currentProgress > oldProgress) { jobHandle.UpdateProgress(this.currentProgress); } } catch (Exception) { // exceptions are ignored // setting progress for administrative purposes should never break job's functionality } } private int loopRangeFrom = 0; private int loopRangeTo = 0; private double allIterations = 0; private double iterationsPassed = 0; /// <summary> /// Initialize loop by specifying number of iterations and progress after loop execution. /// </summary> /// <param name="numIterations"></param> /// <param name="progressAfter"></param> public void StartLoop(int numIterations, int progressAfter) { // Ensure boundary constraints progressAfter = (progressAfter > 100) ? 100 : progressAfter; progressAfter = (progressAfter < 0) ? 0 : progressAfter; // progress should never decrease progressAfter = (progressAfter < loopRangeFrom) ? loopRangeFrom : progressAfter; // set fields this.loopRangeFrom = this.currentProgress; this.loopRangeTo = progressAfter; this.allIterations = numIterations; this.iterationsPassed = 0; } /// <summary> /// This method can be used in a loop to calculate and increase the progress (only if it is necessary). If progress (integer value), /// does not change, the request is not sent to te server, so the job is not needlessly slowed down by the operation. /// StartLoop should be executed before the loop to inform ProgressUpdater about the range. /// </summary> public void ProgressLoop() { this.iterationsPassed++; int newProgress = (int) (loopRangeFrom + (iterationsPassed / allIterations) * ((double)(loopRangeTo - loopRangeFrom))); UpdateProgress(newProgress); } } } |
I used the above code many times, and it didn’t have any problems with it (except that it uses API’s that can’t be used in sandboxed solutions). If you find it useful, feel free to copy and use it in your projects, you might modify the code according to your needs.