Version 2.5
Copyright © 2009, 2010, 2011, 2012 Lars Vogel
02.08.2012
| Revision History | |||
|---|---|---|---|
| Revision 0.1 | 12.07.2009 | Lars Vogel |
Created |
| Revision 0.2 - 2.5 | 15.07.2009 - 02.08.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 tutorial assumes what you have basic understanding of development for the Eclipse platform. Please see Eclipse 4 RCP Tutorial or Eclipse Plug-in Tutorial if you need any basic information.
By default Eclipse uses one
Thread
to run all the code
instructions. This
Thread
runs the event
loop for the user interface (UI) and is
the only
Thread
that is allowed to interact with the UI.
It
is
called the
UI thread
or
main thread.
If another
Thread
tries to update the UI
the
SWT
framework will raise a
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 UI thread, the user interface will not respond to any user interaction.
Therefore
it is
important not to
block
this thread, e.g. with
network or
file
access otherwise the
user interface 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 you to run operations in the
background
using the Eclipse platform.
The
org.eclipse.e4.ui.di
plug-in contains the
UISynchronize
class. An instance of this class is in the Eclipse 4 context and
can
be injected into an Eclipse 4 application via dependency injection.
UISynchronize
provides the
syncExec()
and
asyncExec()
methods to synchronize with the main thread.
Using
UISynchronize
is the preferred method rather than using the
Display.getDefault().asyncExec()
or
Display.getDefault().ayncExec()
methods, as this allows a consistent programming model using
dependency
injection.
The Eclipse Jobs API provides support for running background
processes. It allows you to
provide feedback about the progress of the
Job
to the user via a progress indicator.
The important parts of the Job API are:
IJobManager - Schedules the jobs
Job - The individual task to perform
IProgressMonitor - Interface to communicate information about the status of your Job.
You create a
Job
via the following:
// 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 UI from a
Job
you still need to synchronize yourself with the user interface.
You can set the
Job
priority via the
job.setPriority()
method using the constants defined in the
Job
class, e.g.
Job.SHORT,
Job.LONG,
Job.BUILD
and
Job.DECORATE.
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.DECORATE
are scheduled after all other jobs are finished. Check the
JavaDoc of
the
Job
class for details.
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.
A more advanced approach is to use a
ProgressMonitorDialog
together with an
IRunnableWithProgress.
In Eclipse 3.x API based projects you cannot use dependency injection
to get the
UISynchronize
instance injected.
In this case you can use the
Display
object 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 } });
Eclipse 4 provides the
IEventBrooker
which allows to send events.
Your threads can use the
IEventBrooker
to send event data. Every listener will be automatically called and if
you use the
UIEventTopic
annotation, this method is be automatically called in the user
interface thread.
// Get the IEventBrooker via DI @Inject IEventBroker brooker; // 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) { // TODO Auto-generated catch block e.printStackTrace(); } // Send out an event to update // the UI brooker.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); }
The
IProgressMonitor
object can be used to report progress. Use the
beginTask() method
to define the total units of work and the
worked()
method to report the additional units of work.
Job job = new Job("My 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 { // Sleep a second TimeUnit.SECONDS.sleep(1); monitor.subTask("I'm doing something here " + i); // Report that 20 units are done monitor.worked(20); } catch (InterruptedException e1) { e1.printStackTrace(); return Status.CANCEL_STATUS; } } System.out.println("Called save"); return Status.OK_STATUS; } }; job.schedule();
In Eclipse 4 you can report progress by implementing the
IProgressMonitor
interface.
You can for example add a
ToolItem
to a Toolbar of your Window Trim in your application model. This
ToolItem
can
implement the
IProgressMonitor
interface.
This is demonstrated in the following example.
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 MyToolItem 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().syncExec(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) { sync.syncExec(new Runnable() { @Override public void run() { progressBar.setMaximum(totalWork); progressBar.setToolTipText(name); } }); System.out.println("Starting"); } }
This new element can be accessed via the model service and used as an
IProgressMonitor
for the job.
// EModelService injected as service // Mapplication injected as application Job job = new Job("My 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();
A more advanced implementation could for example implement a Progress Monitoring OSGi Service and report progress to the user interface via the EventAdmin service.
To activate progress reporting in the status line in Eclipse 3.x you
have to activate
progress reporting in
preWindowOpen()
method of the
WorkbenchWindowAdvisor.
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.
Before posting questions, please see the vogella FAQ. If you have questions or find an error in this article please use the www.vogella.com 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
vogella Training Android and Eclipse Training from the vogella team
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