Version 2.3
Copyright © 2009, 2010, 2011, 2012 Lars Vogel
28.01.2012
| Revision History | ||
|---|---|---|
| Revision 0.1 | 12.07.2009 | Lars Vogel |
| Created | ||
| Revision 0.2 - 2.3 | 15.07.2009 - 28.01.2012 | Lars Vogel |
| bug fixes and enhancements | ||
Background processing in Eclipse Plug-ins
This tutorial describes the concept of the main user interface thread in SWT and how to synchronize other threads with this thread.
It also explains the usage of the Jobs API in Eclipse plug-in projects for performing asynchronous tasks.
This tutorial can be used for Eclipse 3.x and Eclipse 4.x based plug-ins.
Table of Contents
This article assumes what you have basic understanding of development for the Eclipse platform. Please see Eclipse RCP Tutorial or Eclipse Plugin Tutorial if you need any further information.
If you don't interfere, Eclipse uses one
Thread
to run all the
instructions in your coding. This
Thread
also
creates the event
loop for the user interface (UI) and is
the only
Thread
that is allowed to interact with the UI.
This
Thread
is
called the "UI thread" or "main thread". It is
created by the
org.eclipse.swt.widgets.Display
class.
If another
Thread,
which is not the UI
Thread, tries to update the UI,
the
SWT
framework will raise an
SWTException
exception.
org.eclipse.swt.SWTException: Invalid thread access
All events in the UI will be executed one after each other. If you perform a long running operation in the UI thread, the user interface will not response to any user interaction.
Therefore it is important not to block this thread, e.g. with network or file access. Otherwise the UI will appear frozen.
All long running operations should be
performed in a separate
Thread.
The Eclipse framework provides ways for a
Thread
to synchronize itself with the user interface. It also provides
the
Eclipse Jobs
Framework which allows to run operations in the background
using the Eclipse platform.
If you need to update the user interface from another
Thread,
you can use the
syncExec()
and
asyncExec()
methods of the
Display
object.
// This will update the UI asynchronously, e.g. the calling thread will continue Display.getDefault().asyncExec(new Runnable() { public void run() { // ... do any work that updates the screen ... } }); // This will update the UI synchronously, e.g. the calling thread will wait // until the work is done Display.getDefault().syncExec(new Runnable() { public void run() { // ... do any work that updates the screen ... } });
The Eclipse Jobs API provides support for running background
processes. It allows to
provide feedback about the progress of the
Job
to the user via a progress indicator.
The important parts of the Job API are:
JobManager - Schedules the jobs
Job - The individual task to perform
IProgressMonitor - UI for Job information
You create a
Job
via the following:
Job job = new Job("My Job") { @Override protected IStatus run(IProgressMonitor monitor) { // Do something long running //... // If you want to update the UI Display.getDefault().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 UI from a
Job
you still need to use the
Display.syncExec()
or
asyncExec()
methods.
You can set the
Job
priority via
job.setPriority()
method using the constants defined in the
Job
class, e.g.
Job.SHORT,
Job.LONG
and
Job.BUILD.
The 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
Job.LONG.
Jobs
with the priority
Job.BUILD
are scheduled after all other jobs are finished. Check the
JavaDoc for
the
Job
class for details.
Sometimes
you simple 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.
A more advanced approach is to use a
ProgressMonitorDialog
together with a
IRunnableWithProgress.
The
IProgressMonitor
object can be used to report progress. Use the
beginTask()
to define the total units of work and the
worked()
method to report the additional units of work the next step
did
perform.
Job job = new Job("Mein Job") { @Override protected IStatus run(IProgressMonitor monitor) { // Set total number of work units monitor.beginTask("Doing something time consuming here", 100); for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); monitor.subTask("I'm doing something here " + i); // Report that 20 units are done monitor.worked(20); } catch (InterruptedException e1) { e1.printStackTrace(); } } System.out.println("Called save"); return Status.OK_STATUS; } }; job.schedule();
To activate progress reporting in the status line in Eclipse 3.x you
have to activate
this in
preWindowOpen()
method of the
WorkbenchWindowAdvisor.
While this was easy to activate, it only supported a infinite progress indicator.
Eclipse 4 does allow that you implement your own
IProgressMonitor.
Eor example you can add an
ToolItem
to a Toolbar of your Window Trim in your application model which
implements
IProgressMonitor.
package com.example.e4.rcp.todo.ui.composites; import javax.annotation.PostConstruct; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.ProgressBar; public class ToolItem implements IProgressMonitor { private ProgressBar progressBar; @PostConstruct public void createControls(Composite parent) { System.out.println("ToolItem"); progressBar = new ProgressBar(parent, SWT.SMOOTH); progressBar.setBounds(100, 10, 200, 20); } @Override public void worked(final int work) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { System.out.println("Worked"); progressBar.setSelection(progressBar.getSelection() + work); } }); } @Override public void subTask(String name) { } @Override public void setTaskName(String name) { } @Override public void setCanceled(boolean value) { } @Override public boolean isCanceled() { return false; } @Override public void internalWorked(double work) { } @Override public void done() { System.out.println("Done"); } @Override public void beginTask(String name, int totalWork) { System.out.println("Starting"); } }
This new element can be access via the model service and used as IProgressMonitor
// EModelService injected as service // Mapplication injected as application Job job = new Job("Mein Job") { // As before }; // Setting the progress monitor IJobManager manager = job.getJobManager(); // ToolItem has the ID "statusbar" in the model MToolControl element = (MToolControl) service.find("statusbar", application); Object widget = element.getObject(); final IProgressMonitor p = (IProgressMonitor) widget; ProgressProvider provider = new ProgressProvider() { @Override public IProgressMonitor createMonitor(Job job) { return p; } }; manager.setProgressProvider(provider); job.schedule();
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.x 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.
Before posting questions, please see the vogella FAQ. If you have questions or find an error in this article please use the www.vogella.de Google Group. I have created a short list how to create good questions which might also help you.
http://www.eclipse.org/articles/Article-Concurrency/jobs-api.html Eclipse Jobs API
Eclipse RCP Training (German) Eclipse RCP Training with Lars Vogel
Android Tutorial Introduction to Android Programming
GWT Tutorial Program in Java and compile to JavaScript and HTML
Eclipse RCP Tutorial Create native applications in Java
JUnit Tutorial Test your application
Git Tutorial Put everything you have under distributed version control system