Wednesday, March 21, 2007

How to report operation progress?

Recently I had to learn a little bit about how to report the progress of an operation. I mean this component which is shown below and which you possibly very often see while using Eclipse:



Never before I had to take care about this, so I knew only that, there were some progress monitors, which I had to pass to some methods. I started digging into the web and found two very good articles: How to Correctly and Uniformly Use Progress Monitors and FAQ How do I use progress monitors? . Both documents contain very useful information and give nice examples. However, these two papers describe the approach in which class SubProgressMonitor is used. This solution is good, but why use good solution, when better one can be used? Since Eclipse 3.3 class SubMonitor is available (in the project org.eclipse.equinox.common in package org.eclipse.core.runtime) and this is a very good news. Why?
Basically because:
  • we don't have to call methods beginTask() and done() on the instance of SubMonitor class
  • it is easier now to create submonitors (there is a method newChild(int totalWork))
  • in SubMonitor we can find method setWorkRemining() which allows us to redistribute the remaining space on the progress monitor
  • SubMonitor protects the caller from common progress reporting bugs in a called method
I was trying to find some information in the Web concerning SubMonitor, but I failed. Fortunately I found useful info in the javadoc, so I decided to make it more public to make this solution more popular.
First of all we have to convert any other kind of progress monitor into SubMonitor. This is very easy, because we can use one of two static methods:
public
static SubMonitor convert(IProgressMonitor monitor, int work)

and
public
static SubMonitor convert(IProgressMonitor monitor, String taskName, int work)

So after invoking:
SubMonitor progress = SubMonitor.convert(monitor, 100);

we can benefit from all the advances of SubMonitor class.
The basic example of using SubMonitor could look like this:

void someOperation(IProgressMonitor monitor) {

// Convertion of the given monitor
SubMonitor progressSubMonitor = SubMonitor.convert(monitor, 100);

// 70% of work is consumed
doSomeWork(progressSubMonitor.newChild(70));

// 30% of work is consumed
progressSubMonitor.worked(30);

}

My main concern about reporting the progress was how to manipulate the number of work items, when some operation will or will not be executed (so when we have a condition). Fortunately with SubMonitor it is pretty easy. The example below shows how to do this:

void
someOperation(IProgressMonitor monitor) {

SubMonitor progressSubMonitor = SubMonitor.convert(monitor, 100);

if (condition) {
// 50% of work is consumed

doSomeWork(progressSubMonitor.newChild(40));
}

//if there is any work item resumed from the first 40% then use it

progress.setWorkRemaining(40);

// the rest of work items is consumed

doSomeWork(progress.newChild(60));

}


It is also easy to handle the loop case:

void someOperation(IProgressMonitor monitor, Collection someCollection) {

SubMonitor progressSubMonitor = SubMonitor.convert(monitor, 100);

// Create a new progress monitor that uses 70% of the total progress
SubMonitor loopProgress = progressSubMonitor.newChild(70);

// Allocate one tick for each element of the given collection.
loopProgress.setWorkRemaining(someCollection.size());

for (Iterator iter = someCollection.iterator(); iter.hasNext();) {
Object next = iter.next();
//Do something with the element from collection and
//consume one work item
doSomeWorkOnElement(next, loopProgress.newChild(1));
}

// Use the remaining 30% of the progress monitor to do some work outside
// the loop
doSomeWork(progressSubMonitor .newChild(30));

}

Have fun with using SubMonitor ;-)

No comments: