One of the major challenges of a complex system is to remain responsive while tasks are being performed. This challenge is even greater in an extensible system, when components that weren't designed to run together are sharing the same resources. The org.eclipse.core.runtime.jobs package addresses this challenge by providing infrastructure for scheduling, executing, and managing concurrently running operations. This infrastructure is based on the use of jobs to represent a unit of work that can run asynchronously.
class TrivialJob extends Job { public TrivialJob() { super("Trivial Job"); } public IStatus run(IProgressMonitor monitor) { System.out.println("This is a job"); return Status.OK_STATUS; } }The job is created and scheduled in the following snippet:
TrivialJob job = new TrivialJob(); System.out.println("About to schedule a job"); job.schedule(); System.out.println("Finished scheduling a job");The output of this program is timing dependent. That is, there is no way to be sure when the job's run method will execute in relation to the thread that created the job and scheduled it. The output will either be:
About to schedule a job This is a job Finished scheduling a jobor:
About to schedule a job Finished scheduling a job This is a job
If you want to be certain that a job has completed before continuing, you can use
the join() method. This method will block the caller until the job has completed, or
until the calling thread is interrupted. Let's rewrite our snippet from above in a more
deterministic manner:
TrivialJob job = new TrivialJob();
System.out.println("About to schedule a job");
job.schedule();
job.join();
if (job.getResult().isOk())
System.out.println("Job completed with success");
else
System.out.println("Job did not complete successfully");
Assuming the join() call is not interrupted, this method is guaranteed to
return the following result:
About to schedule a job
This is a job
Job completed with success
Of course, it is generally not useful to join a job immediately after scheduling it, since you obtain no concurrency by doing so. In this case you might as well do the work from the job's run method directly in the calling thread. We'll look at some examples later on where the use of join makes more sense.
The last snippet also makes use of the job result. The result is the IStatus object that is returned from the job's run() method. You can use this result to pass any necessary objects back from the job's run method. The result can also be used to indicate failure (by returning an IStatus with severity IStatus.ERROR), or cancellation (IStatus.CANCEL).
We've seen how to schedule a job and wait for it complete, but there are many other
interesting things you can to do jobs. If you
schedule a job but then decide it is no longer needed, the job can be stopped using
the cancel() method. If the job has not yet started running when canceled,
the job is immediately discarded and will not run. If, on the other hand, the job
has already started running, it is up to the job whether it wants to respond to the cancellation.
When you are trying to cancel a job, waiting for it using the join() method comes in handy.
Here is a common idiom for canceling a job, and waiting until the job is finished before
proceeding:
if (!job.cancel())
job.join();
If the cancellation does not take effect immediately, then cancel() will return
false and the caller will use join() to wait for the job to successfully cancel.
Slightly less drastic than cancellation is the sleep() method. Again, if the job has not yet started running, this method will cause the job to be put on hold indefinitely. The job will still be remembered by the platform, and a wakeUp() call will cause the job to be added to the wait queue where it will eventually be executed.
A job goes through several states during its lifetime. Not only can it be manipulated through API such as cancel() and sleep(), but its state also changes as the platform runs and completes the job. Jobs can move through the following states:
A job can only be put to sleep if it is currently WAITING. Waking up a sleeping job will put it back in the WAITING state. Canceling a job will return it to the NONE state.
If your plug-in needs to know the state of a particular job, it can register a job change listener that is notified as the job moves through its life-cycle. This is useful for showing progress or otherwise reporting on a job.
The Job method addJobChangeListener can be used to register a listener on a particular job. IJobChangeListener defines protocol for responding to the state changes in a job:
In all of these cases, the listener is provided with an IJobChangeEvent that specifies the job undergoing the state change and status on its completion (if it is done).
Note: Jobs also define the getState() method for obtaining the (relatively) current state of a job. However, this result is not always reliable since jobs run in a different thread and may change state again by the time the call returns. Job change listeners are the recommended mechanism for discovering state changes in a job.
IJobManager
defines protocol for working with all of the jobs in the system. Plug-ins that show progress or otherwise
work with the job infrastructure can use IJobManager
to perform tasks such as suspending all jobs in the system, finding out which job is running, or receiving progress
feedback about a particular job. The platform's job manager can be obtained using
Platform API:
IJobManager jobMan = Platform.getJobManager();
Plug-ins interested in the state of all jobs in the system can register a job change listener on the job manager rather
than registering listeners on many individual jobs.
It is sometimes easier for a plug-in to work with a group of related jobs as a single unit.
This can be accomplished using job families. A job declares that it belongs to a certain family by
overriding the belongsTo method:
public static final String MY_FAMILY = "myJobFamily";
...
class FamilyJob extends Job {
...
public boolean belongsTo(Object family) {
return family == MY_FAMILY;
}
}
IJobManager
protocol can be used to cancel, join, sleep, or find all jobs in a family:
IJobManager jobMan = Platform.getJobManager();
jobMan.cancel(MY_FAMILY);
jobMan.join(MY_FAMILY, null);
Since job families are represented using arbitrary objects, you can store interesting
state in the job family itself, and jobs can dynamically build family objects as needed.
It is important to use family objects that are
fairly unique, to avoid accidental interaction with the families created by
other plug-ins.
Families are also a convenient way of locating groups of jobs. The method IJobManager.find(Object family) can be used to locate instances of all running, waiting, and sleeping jobs at any given time.