Concurrency infrastructure

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.

 

Jobs

The Job class represents a unit of asynchronous work running concurrently with other jobs. To perform a task, a plug-in creates a job and then schedules it. Once a job is scheduled, it is added to a job queue managed by the platform. The platform uses a background scheduling thread to manage all of the pending jobs. As a running job completes, it is removed from the queue and the platform decides which job to run next. When a job becomes active, the platform invokes its run() method. Jobs are best demonstrated with a simple example:
   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 job
or:
   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).

 

Common job operations

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.

 

Job states

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.

 

Job change listeners

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.

 

The job manager

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.

 

Job families

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.