NOW Hiring

Quick links

Eclipse Jobs and Background Processing - Tutorial

Lars Vogel

Simon Scholz

Version 3.5

22.03.2016

Background processing in Eclipse Plug-ins

This tutorial describes how to do asynchronous work within Eclipse plug-ins and RCP applications. It explains the usage of the Jobs API for performing asynchronous tasks.

This tutorial can be used for Eclipse 3.x and Eclipse 4.x based plug-ins.


Table of Contents

1. Prerequisites for this tutorial
2. Eclipse background processing
2.1. Main thread
2.2. Using dependency injection and UISynchronize
2.3. Eclipse Jobs API
2.4. Priorities of Jobs
2.5. Blocking the UI and providing feedback
3. Asynchronous processing and the event bus
4. Reporting Progress
4.1. IProgressMonitor and the SubMonitor
4.2. Taking conditions during progress into account
4.3. Reporting progress in Eclipse RCP applications
5. Reporting Progress in Eclipse 3.x
6. Handle Job cancellation
6.1. Handle Job cancellation before Eclipse Neon (4.6)
6.2. Handling Job cancellation since Eclipse Neon (4.6)
7. Tutorial: Using Eclipse Jobs
8. Using syncExec() and asyncExec()
9. Learn more about Eclipse 4 RCP development
10. About this website
11. Links and Literature
11.1. Eclipse Jobs resources
11.2. vogella GmbH training and consulting support
Get the book! Eclipse RCP book

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 but can create multiple threads.

By default the Eclipse framework uses a single thread to run all the code instructions. This thread runs the event loop for the application and 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 = new Job("My Job") {
  @Override
  protected IStatus run(IProgressMonitor monitor) {
    // do something long running
    //... 
            
    // If you want to update the UI
    sync.asyncExec(new Runnable() {
      @Override
      public void run() {
        // do something in the user interface
        // e.g. set a text field
      }
    });
    return Status.OK_STATUS;
  }
};

// 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. Asynchronous processing and the event bus

Your threads can use the IEventBroker to send event data. Every listener will be automatically called and if a method is annotated with the UIEventTopic annotation, it will be called in the main thread.

private static final String UPDATE ="update";

// get the IEventBroker injected
@Inject
IEventBroker broker;

// somewhere in you code you do something 
// performance intensive

button.addSelectionListener(new SelectionAdapter() {
      @Override
      public void widgetSelected(SelectionEvent e) {
        Runnable runnable = new Runnable() {
          public void run() {
            for (int i = 0; i < 10; i++) {
              try {
                Thread.sleep(500);
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
              // send out an event to update
              // the UI
              broker.send(UPDATE, i);
            }
          }
        };
        new Thread(runnable).start();
      }
    });



// more code
// ....


// get notified and sync automatically
// with the UI thread

@Inject @Optional
public void  getEvent(@UIEventTopic(UPDATE) int i) {
  // text1 is a SWT Text field
  text1.setText(String.valueOf(i));
  System.out.println(i);
} 

4. Reporting Progress

4.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.

4.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().

4.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.example.e4.rcp.todo.toolcontrols;


import java.util.Objects;

import javax.annotation.PostConstruct;
import javax.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);
    
    monitor = new GobalProgressMonitor();
    
    Job.getJobManager().setProgressProvider(new ProgressProvider() {
      @Override
      public IProgressMonitor createMonitor(Job job) {
        return monitor.addJob(job);
      }
    });
  }

  private final class GobalProgressMonitor 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(); 

Tip

A more advanced implementation could, for example, implement a progress monitoring OSGi Service and report progress to the user interface via the event service.

5. 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.

6. 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.

6.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(); 

6.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.

Note

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.

7. 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.

8. 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
  }
}); 

9. Learn more about Eclipse 4 RCP development

I hope you enjoyed this tutorial. You find this tutorial and much more information also in the Eclipse 4 RCP book from this author.

10. About this website

11. Links and Literature

11.1. Eclipse Jobs resources

Eclipse Jobs API tutorial

11.2. vogella GmbH training and consulting support

TRAINING SERVICE & SUPPORT
The vogella company provides comprehensive training and education services from experts in the areas of Eclipse RCP, Android, Git, Java, Gradle and Spring. We offer both public and inhouse training. Whichever course you decide to take, you are guaranteed to experience what many before you refer to as “The best IT class I have ever attended”. The vogella company offers expert consulting services, development support and coaching. Our customers range from Fortune 100 corporations to individual developers.