Home Tutorials Training Consulting Books Company Contact us


Get more...

This tutorial describes how to do asynchronous work within Eclipse plug-ins and RCP applications via the Jobs API.

1. Prerequisites for this tutorial

This tutorial assumes that you have basic understanding of development for the Eclipse platform. Please see Eclipse RCP Tutorial or Eclipse Plug-in Tutorial if you need any basic information.

2. Eclipse background processing

2.1. Main thread

An Eclipse RCP application runs in one process. By default, the Eclipse framework uses a single thread to run all the code instructions. This

This thread runs the event loop for the application. It is the only thread that is allowed to interact with the user interface (UI). It is called the main thread. Sometimes it is also called the UI thread, but this is a misnomer as it handles all events not only the ui events.

If another thread tries to update the UI, the Eclipse framework throws an SWTException exception.

org.eclipse.swt.SWTException: Invalid thread access

All events in the user interface are executed one after another. If you perform a long running operation in the main thread, the application does not respond to user interaction during the execution time of this operation.

Blocking the user interaction is considered a bad practice. Therefore it is important to perform all long running operations in a separate thread. Long running operations are, for example, network or file access.

As only the main thread is allowed to modify the user interface, the Eclipse framework provides ways for a thread to synchronize itself with the main thread. It also provides the Eclipse Jobs framework which allows you to run operations in the background and providing feedback of the job status to the Eclipse platform.

2.2. Using dependency injection and UISynchronize

The org.eclipse.e4.ui.di plug-in contains the UISynchronize class. An instance of this class can be injected into an Eclipse application via dependency injection.

UISynchronize provides the syncExec() and asyncExec() methods to synchronize with the main thread.

2.3. Eclipse Jobs API

The Eclipse Jobs API provides support for running background processes and providing feedback about the progress of the Job.

The important parts of the Job API are:

  • IJobManager - schedules jobs

  • Job - the individual task to perform

  • IProgressMonitor - interface to communicate information about the status of your Job.

The creation and scheduling of a Job is demonstrated in the following code snippet.

// get UISynchronize injected as field
@Inject UISynchronize sync;

// more code

Job job = Job.create("Update table", (ICoreRunnable) monitor -> {
    // do something long running
    // ...

    // If you want to update the UI
    sync.asyncExec(() -> {
        // do something in the user interface
        // e.g. set a text field
    });
});

// Start the Job
job.schedule();

If you want to update the user interface from a Job, you need to synchronize the corresponding action with the user interface similar to the direct usage of threads.

2.4. Priorities of Jobs

You can set the Job priority via the job.setPriority() method. The Job class contains predefined priorities, e.g. Job.SHORT, Job.LONG, Job.BUILD and Job.DECORATE.

The Eclipse job scheduler will use these priorities to determine in which order the Jobs are scheduled. For example, jobs with the priority Job.SHORT are scheduled before jobs with the Job.LONG priority. Check the JavaDoc of the Job class for details.

2.5. Blocking the UI and providing feedback

Sometimes you simply want to give the user the feedback that something is running without using threads.

The easiest way to provide feedback is to change the cursor via the BusyIndicator.showWhile() method call.

// Show a busy indicator while the runnable is executed
BusyIndicator.showWhile(display, runnable);

If this code is executed, the cursor will change to a busy indicator until the Runnable is done.

3. Reporting Progress

3.1. IProgressMonitor and the SubMonitor

An instance of an IProgressMonitor is passed to the run method of a Job and can be used to monitor the progress of the job. When methods inside the job need to report feedback on the progress, a child of the Submonitor is passed rather than passing the actual SubMonitor. It is good practice to convert the IProgressMonitor always to a SubMonitor before using it. This allows to use a consistent API for process reporting of child processes and the main process. A SubMonitor can be created similar to this: SubMonitor subMonitor = SubMonitor.convert(monitor, 3);

final List<Task> tasks = taskService.getTasks();
Job job = new Job("My Job") {
    @Override
    protected IStatus run(IProgressMonitor monitor) {
        // convert to SubMonitor and set total number of work units
        SubMonitor subMonitor = SubMonitor.convert(monitor, tasks.size());
        for (Task task : tasks) {
            try {
                // sleep a second
                TimeUnit.SECONDS.sleep(1);

                // set the name of the current work
                subMonitor.setTaskName("I'm working on Task " + task.getSummary());
                
                // workOnTask is a method in this class which does some work
                // pass a new child with the totalWork of 1 to the mehtod
                workOnTask(task, subMonitor.split(1));
                
            } catch (InterruptedException e) {
                return Status.CANCEL_STATUS;
            }
        }
        return Status.OK_STATUS;
    }

};
job.schedule();

When using a SubMonitor it is not necessary to call the beginTask() method or the done() method, since this is done implicitly by the SubMonitor implementation.

3.2. Taking conditions during progress into account

In some cases the amount of work depends on conditions, which should also be properly reported.

This can be done by using the setWorkRemaining() method of the SubMonitor.

protected IStatus run(IProgressMonitor monitor) {
    // convert to SubMonitor and set total number of work units
    SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
    
    if(taskNeedsPreconfiguration(task)) {
        // Takes 30 % of the work
        preConfigureTask(task, subMonitor.split(30));
    }
    
    // ensure only 70 % of the work remains
    subMonitor.setWorkRemaining(70);
    
    // do the rest of the work
    workOnTask(task, subMonitor.split(70));
    
    return Status.OK_STATUS;
}

In case the code in the taskNeedsPreconfiguration() if block is run, the setWorkRemaining() method actually does nothing. Only in case the code of the if block is skipped it ensures that the process monitoring is done properly.

Another use case for using setWorkRemaining() is when the actual work is determined later. See workOnTask method.

protected IStatus run(IProgressMonitor monitor) {
    // convert to SubMonitor and set total number of work units
    SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
    
    if(taskNeedsPreconfiguration(task)) {
        // Takes 30 % of the work
        preConfigureTask(task, subMonitor.split(30));
    }
    
    // ensure only 70 % of the work remains
    subMonitor.setWorkRemaining(70);
    
    // do the rest of the work
    workOnTask(task, subMonitor.split(70));
    
    
    return Status.OK_STATUS;
}

private void preConfigureTask(Task task, IProgressMonitor monitor) {
    SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
    subMonitor.setTaskName("Preconfiguring Task " + task.getSummary());
    
    // ... do the configuration
}

private void workOnTask(Task task, IProgressMonitor monitor) {
    SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
    subMonitor.setTaskName("Work on Task " + task.getSummary());
    
    SubMonitor loopRelatedTasksMonitor = subMonitor.split(80);
    
    // get related tasks from a service, which should also be preconfigured
    List<Task> relatedTasks = taskService.findRelatedTasks(task);
    
    // setWorkRemaining can also be applied, if the actual work is determined later
    loopRelatedTasksMonitor.setWorkRemaining(relatedTasks.size());
    
    for (Task relatedtask : relatedTasks) {
        preConfigureTask(relatedtask, loopRelatedTasksMonitor.split(1));
    }

    // ... do work on the actual task with the remaining 20 %
    doWorkOnActualTask(task, subMonitor.split(20));
}

For the loop in the workOnTask a new child SubMonitor, which is supposed to do 80 % of the work, is created. Later on the actual remaining work for this SubMonitor is set by the setWorkRemaining().

3.3. Reporting progress in Eclipse RCP applications

In Eclipse applications you can report progress by implementing the IProgressMonitor interface.

You can, for example, add a tool control to a toolbar in your application model. This tool control can implement the IProgressMonitor interface to show the progress.

This is demonstrated in the following example.

package com.vogella.tasks.ui.toolcontrols;


import java.util.Objects;

import jakarta.annotation.PostConstruct;
import jakarta.inject.Inject;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.core.runtime.jobs.JobChangeAdapter;
import org.eclipse.core.runtime.jobs.ProgressProvider;
import org.eclipse.e4.ui.di.UISynchronize;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.ProgressBar;

public class ProgressMonitorControl {
  
  private final UISynchronize sync;
  
  private ProgressBar progressBar;
  private GobalProgressMonitor monitor;
  
  @Inject
  public ProgressMonitorControl(UISynchronize sync) {
    this.sync = Objects.requireNonNull(sync);    
  }
  
  @PostConstruct
  public void createControls(Composite parent){
    progressBar = new ProgressBar(parent, SWT.SMOOTH);
    progressBar.setBounds(100, 10, 200, 20);
    
    Job.getJobManager().setProgressProvider(new ProgressProvider() {

        @Override
        public IProgressMonitor createMonitor(Job job) {

            StatusUpdateProgressMonitor statusUpdateProgressMonitor = new StatusUpdateProgressMonitor();
            return statusUpdateProgressMonitor.addJob(job);
        }
    });

    private final class StatusUpdateProgressMonitor extends NullProgressMonitor {

    // thread-Safe via thread confinement of the UI-Thread 
    // (means access only via UI-Thread)
    private long runningTasks = 0L;
    
    @Override
    public void beginTask(final String name, final int totalWork) {
      sync.syncExec(new Runnable() {
        
        @Override
        public void run() {
          if( runningTasks <= 0 ) {
            // --- no task is running at the moment ---
            progressBar.setSelection(0);
            progressBar.setMaximum(totalWork);
            
          } else {
            // --- other tasks are running ---
            progressBar.setMaximum(progressBar.getMaximum() + totalWork);
          }
          
          runningTasks++;
          progressBar.setToolTipText("Currently running: " + runningTasks + 
                  "\nLast task: " + name);
        }
      });
    }
  
    @Override
    public void worked(final int work) {
      sync.syncExec(new Runnable() {
  
        @Override
        public void run() {
          progressBar.setSelection(progressBar.getSelection() + work);
        }
      });
    }
    
    public IProgressMonitor addJob(Job job){
      if( job != null ){
        job.addJobChangeListener(new JobChangeAdapter() {
          @Override
          public void done(IJobChangeEvent event) {
            sync.syncExec(new Runnable() {
              
              @Override
              public void run() {
                runningTasks--;
                if (runningTasks > 0 ){
                  // --- some tasks are still running ---
                  progressBar.setToolTipText("Currently running: " + runningTasks);
                  
                } else {
                  // --- all tasks are done (a reset of selection could also be done) ---
                  progressBar.setToolTipText("No background progress running.");
                }
              }
            });
            
            // clean-up
            event.getJob().removeJobChangeListener(this);
          }
        });
      }
      return this;
    }
  }
}

This new element can be accessed via the model service and used as an IProgressMonitor for the job.

Job job = new Job("My Job") {
    // code as before
};
job.schedule();
A more advanced implementation could, for example, implement a progress monitoring OSGi Service and report progress to the user interface via the event service.

4. Reporting Progress in Eclipse 3.x

To activate progress reporting in the status line in Eclipse 3.x you have to activate progress reporting in preWindowOpen() method of the WorkbenchWindowAdvisor.

5. Handle Job cancellation

In Eclipse many user complain about jobs, which are not properly canceled when pressing the red square cancel button in the progess view. Some Job implementations simply just don’t care and do not react on the cancellation, which is triggered by the user.

This of course concludes in a really poor user experience, which should definitely be avoided.

In order to realize that a user has canceled a Job the IProgressMonitor, which is passed to the run method of a Job, can be asked whether the Job has been canceled. This can be done by calling the isCanceled on an IProgressMonitor.

This check for cancellation should be done in a frequent manner, so that the user does not have to wait too long until the job will really be canceled.

5.1. Handle Job cancellation before Eclipse Neon (4.6)

Job job = Job.create("CancelAbleJob", monitor -> {
    SubMonitor subMonitor = SubMonitor.convert(monitor, todos.size());
    
    for (Todo todo : todos) {
        if(subMonitor.isCanceled()) {
            return Status.CANCEL_STATUS;
        }
        
        processTodo(subMonitor.newChild(1), todo);
    }
    
    return Status.OK_STATUS;
});
job.schedule();

5.2. Handling Job cancellation since Eclipse Neon (4.6)

Job job = Job.create("CancelAbleJob", monitor -> {
    SubMonitor subMonitor = SubMonitor.convert(monitor, todos.size());
    
    for (Todo todo : todos) {
        processTodo(subMonitor.split(1), todo);
    }
    
    // no return value needed when using an ICoreRunnable (since Neon)
});
job.schedule();

The new split method can handle the cancellation check automatically and throws an OperationCanceledException in case the IProgressMonitor has been canceled. The OperationCanceledException is automatically caught by a Job, which will just set the Status.CANCEL_STATUS then.

Using the new Submonitor’s split is not only shorter than the former approach, but also provides better performance since the expensive isCanceled call is only done in case it is really necessary.

6. Tutorial: Using Eclipse Jobs

Create a new Eclipse plug-in project "de.vogella.jobs.first" with a View and a Button included in this View.

Create the following MySelectionAdapter class.

package de.vogella.jobs.first.parts;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class MySelectionAdapter extends SelectionAdapter {
    private final Shell shell;

    public MySelectionAdapter(Shell shell) {
        this.shell = shell;
    }

    @Override
    public void widgetSelected(SelectionEvent e) {
        Job job = new Job("First Job") {
            @Override
            protected IStatus run(IProgressMonitor monitor) {
                doLongThing();
                syncWithUi();
                // use this to open a Shell in the UI thread
                return Status.OK_STATUS;
            }

        };
        job.setUser(true);
        job.schedule();
    }

    private void doLongThing() {
        for (int i = 0; i < 10; i++) {
            try {
                // We simulate a long running operation here
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Doing something");
        }
    }

    private void syncWithUi() {
        Display.getDefault().asyncExec(new Runnable() {
            public void run() {
                MessageDialog.openInformation(shell, "Your Popup ",
                        "Your job has finished.");
            }
        });

    }
}

Add an instance of MySelectionAdapter as SelectionListener to your Button.

Button button = new Button(parent, SWT.PUSH);
button.addSelectionListener(new MySelectionAdapter(shell));

To access the Shell in Eclipse 3.x you can use the getSite().getShell() method call. In Eclipse 4 you declare a field and let Eclipse inject the Shell.

@Inject Shell shell

Start your application or the Eclipse workbench with your plug-in and press the Button. A dialog is opened.

7. Using syncExec() and asyncExec()

In Eclipse 3.x API based plug-ins you cannot use dependency injection to get the UISynchronize instance injected.

In this case you can use the Display class which provides the syncExec() and asyncExec() methods to update the user interface from another thread.

// Update the user interface asynchronously
Display.getDefault().asyncExec(new Runnable() {
    public void run() {
        // ... do any work that updates the screen ...
    }
});

// Update the user interface synchronously

Display.getDefault().syncExec(new Runnable() {
    public void run() {
        // do any work that updates the screen ...
        // remember to check if the widget
        // still exists
        // might happen if the part was closed
    }
});

8. Learn more and get support

This tutorial continues on Eclipse RCP online training or Eclipse IDE extensions with lots of video material, additional exercises and much more content.

9. Eclipse Jobs resources