Swingworker in Java

How do I use SwingWorker in Java?

Generally, SwingWorker is used to perform long-running tasks in Swing.

Running long-running tasks on the Event Dispatch Thread (EDT) can cause the GUI to lock up, so one of the things which were done is to use SwingUtilities.invokeLater and invokeAndWait which keeps the GUI responsive by which prioritizing the other AWT events before running the desired task (in the form of a Runnable).

However, the problem with SwingUtilities is that it didn't allow returning data from the the executed Runnable to the original method. This is what SwingWorker was designed to address.

The Java Tutorial has a section on SwingWorker.

Here's an example where a SwingWorker is used to execute a time-consuming task on a separate thread, and displays a message box a second later with the answer.

First off, a class extending SwingWorker will be made:

class AnswerWorker extends SwingWorker<Integer, Integer>
{
protected Integer doInBackground() throws Exception
{
// Do a time-consuming task.
Thread.sleep(1000);
return 42;
}

protected void done()
{
try
{
JOptionPane.showMessageDialog(f, get());
}
catch (Exception e)
{
e.printStackTrace();
}
}
}

The return type of the doInBackground and get methods are specified as the first type of the SwingWorker, and the second type is the type used to return for the publish and process methods, which are not used in this example.

Then, in order to invoke the SwingWorker, the execute method is called. In this example, we'll hook an ActionListener to a JButton to execute the AnswerWorker:

JButton b = new JButton("Answer!");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
new AnswerWorker().execute();
}
});

The above button can be added to a JFrame, and clicked on to get a message box a second later. The following can be used to initialize the GUI for a Swing application:

private void makeGUI()
{
final JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new FlowLayout());

// include: "class AnswerWorker" code here.
// include: "JButton" b code here.

f.getContentPane().add(b);
f.getContentPane().add(new JButton("Nothing"));
f.pack();
f.setVisible(true);
}

Once the application is run, there will be two buttons. One labeled "Answer!" and another "Nothing". When one clicks on the "Answer!" button, nothing will happen at first, but clicking on the "Nothing" button will work and demonstrate that the GUI is responsive.

And, one second later, the result of the AnswerWorker will appear in the message box.

SwingWorker in another SwingWorker's done method

SwingWorker supports PropertyChange events, that is, you can listener to when the SwingWorker changes state or updates it's progress...yes, SwingWorker even supports progress notification, for example

This means you could set up a PropertyChangeListener to monitor changes to the progress and state properties and take appropriate actions...

A worker that simple sets progress updates...

public class LoadMaster extends SwingWorker<Void, Progress> {

@Override
protected Void doInBackground() throws Exception {
System.out.println("Working hard here, nothing to see...");
for (int index = 0; index < 100; index++) {
Thread.sleep(10);
setProgress(index);
}
return null;
}

@Override
protected void done() {
try {
get();
} catch (Exception e) {
}
}

}

A example PropertyChangeListener...

public class LoadMasterPropertyChanegHandler implements PropertyChangeListener {

private SwingWorkerExample example;

public LoadMasterPropertyChanegHandler(SwingWorkerExample example) {
this.example = example;
}

@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
int value = (int) evt.getNewValue();
example.showProgress(value);
} else if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
if (worker.isDone()) {
try {
worker.get();
example.loadCompleted();
} catch (InterruptedException | ExecutionException exp) {
example.loadFailed();
}
}
}
}

}

Now, all this does is sends back information to the SwingWorkerExample (it's coming) which allows it to determine what it should do...

In this example, the loadCompleted method updates the UI and then starts the second worker...

protected void loadCompleted() {
//...
LoadStuffWorker stuffWorker = new LoadStuffWorker(this);
stuffWorker.execute();
}

In all truth, I might use interfaces instead, so I'm not overtly exposing the class, but that's a topic for another day...

And the full example...

Worker

import java.awt.EventQueue;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;

public class SwingWorkerExample {

private JProgressBar pb;
private JPanel content;

public static void main(String[] args) {

new SwingWorkerExample();

}

public SwingWorkerExample() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}

pb = new JProgressBar();
content = new JPanel();
content.setBorder(new EmptyBorder(10, 10, 10, 10));

JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(content);
frame.setLayout(new GridBagLayout());
frame.add(pb);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);

LoadMaster master = new LoadMaster();
master.addPropertyChangeListener(new LoadMasterPropertyChanegHandler(SwingWorkerExample.this));
master.execute();
}
});
}

protected void showProgress(int value) {
pb.setValue(value);
}

protected void loadCompleted() {
content.removeAll();
content.setLayout(new GridLayout(0, 1));
content.add(new JLabel("All your base are belong to us"));
content.revalidate();

LoadStuffWorker stuffWorker = new LoadStuffWorker(this);
stuffWorker.execute();
}

protected void loadFailed() {
content.removeAll();
content.setLayout(new GridLayout(0, 1));
content.add(new JLabel("Fail"));
content.revalidate();
}

protected void setVersion(String value) {
content.add(new JLabel("Version: " + value));
content.revalidate();
}

protected void failed(String fail) {
content.add(new JLabel(fail));
content.revalidate();
}

public class LoadMaster extends SwingWorker<Void, Progress> {

@Override
protected Void doInBackground() throws Exception {
System.out.println("Working hard here, nothing to see...");
for (int index = 0; index < 100; index++) {
Thread.sleep(10);
setProgress(index);
}
return null;
}

@Override
protected void done() {
try {
get();
} catch (Exception e) {
}
}

}

public class LoadStuffWorker extends SwingWorker<String, Void> {

private SwingWorkerExample example;

public LoadStuffWorker(SwingWorkerExample example) {
this.example = example;
}

@Override
protected String doInBackground() throws Exception {
System.out.println("Hanging about in the background");
Thread.sleep(3000);
return "Hello from the dark side";
}

@Override
protected void done() {
try {
String value = get();
example.setVersion(value);
} catch (InterruptedException | ExecutionException ex) {
example.failed("Fail while doing version check");
}
}

}

public class Progress {
}

public class LoadMasterPropertyChanegHandler implements PropertyChangeListener {

private SwingWorkerExample example;

public LoadMasterPropertyChanegHandler(SwingWorkerExample example) {
this.example = example;
}

@Override
public void propertyChange(PropertyChangeEvent evt) {
System.out.println(evt.getPropertyName());
if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
int value = (int) evt.getNewValue();
example.showProgress(value);
} else if ("state".equalsIgnoreCase(evt.getPropertyName())) {
SwingWorker worker = (SwingWorker) evt.getSource();
if (worker.isDone()) {
try {
worker.get();
example.loadCompleted();
} catch (InterruptedException | ExecutionException exp) {
example.loadFailed();
}
}
}
}

}

}

SwingWorker, done() is executed before process() calls are finished

SHORT ANSWER:

This happens because publish() doesn't directly schedule process, it sets a timer which will fire the scheduling of a process() block in the EDT after DELAY. So when the worker is cancelled there is still a timer waiting to schedule a process() with the data of the last publish. The reason for using a timer is to implement the optimization where a single process may be executed with the combined data of several publishes.

LONG ANSWER:

Let's see how publish() and cancel interact with each other, for that, let us dive into some source code.

First the easy part, cancel(true):

public final boolean cancel(boolean mayInterruptIfRunning) {
return future.cancel(mayInterruptIfRunning);
}

This cancel ends up calling the following code:

boolean innerCancel(boolean mayInterruptIfRunning) {
for (;;) {
int s = getState();
if (ranOrCancelled(s))
return false;
if (compareAndSetState(s, CANCELLED)) // <-----
break;
}
if (mayInterruptIfRunning) {
Thread r = runner;
if (r != null)
r.interrupt(); // <-----
}
releaseShared(0);
done(); // <-----
return true;
}

The SwingWorker state is set to CANCELLED, the thread is interrupted and done() is called, however this is not SwingWorker's done, but the future done(), which is specified when the variable is instantiated in the SwingWorker constructor:

future = new FutureTask<T>(callable) {
@Override
protected void done() {
doneEDT(); // <-----
setState(StateValue.DONE);
}
};

And the doneEDT() code is:

private void doneEDT() {
Runnable doDone =
new Runnable() {
public void run() {
done(); // <-----
}
};
if (SwingUtilities.isEventDispatchThread()) {
doDone.run(); // <-----
} else {
doSubmit.add(doDone);
}
}

Which calls the SwingWorkers's done() directly if we are in the EDT which is our case. At this point the SwingWorker should stop, no more publish() should be called, this is easy enough to demonstrate with the following modification:

while(!isCancelled()) {
textArea.append("Calling publish\n");
publish("Writing...\n");
}

However we still get a "Writing..." message from process(). So let us see how is process() called. The source code for publish(...) is

protected final void publish(V... chunks) {
synchronized (this) {
if (doProcess == null) {
doProcess = new AccumulativeRunnable<V>() {
@Override
public void run(List<V> args) {
process(args); // <-----
}
@Override
protected void submit() {
doSubmit.add(this); // <-----
}
};
}
}
doProcess.add(chunks); // <-----
}

We see that the run() of the Runnable doProcess is who ends up calling process(args), but this code just calls doProcess.add(chunks) not doProcess.run() and there's a doSubmit around too. Let's see doProcess.add(chunks).

public final synchronized void add(T... args) {
boolean isSubmitted = true;
if (arguments == null) {
isSubmitted = false;
arguments = new ArrayList<T>();
}
Collections.addAll(arguments, args); // <-----
if (!isSubmitted) { //This is what will make that for multiple publishes only one process is executed
submit(); // <-----
}
}

So what publish() actually does is adding the chunks into some internal ArrayList arguments and calling submit(). We just saw that submit just calls doSubmit.add(this), which is this very same add method, since both doProcess and doSubmit extend AccumulativeRunnable<V>, however this time around V is Runnable instead of String as in doProcess. So a chunk is the runnable that calls process(args). However the submit() call is a completely different method defined in the class of doSubmit:

private static class DoSubmitAccumulativeRunnable
extends AccumulativeRunnable<Runnable> implements ActionListener {
private final static int DELAY = (int) (1000 / 30);
@Override
protected void run(List<Runnable> args) {
for (Runnable runnable : args) {
runnable.run();
}
}
@Override
protected void submit() {
Timer timer = new Timer(DELAY, this); // <-----
timer.setRepeats(false);
timer.start();
}
public void actionPerformed(ActionEvent event) {
run(); // <-----
}
}

It creates a Timer that fires the actionPerformed code once after DELAY miliseconds. Once the event is fired the code will be enqueued in the EDT which will call an internal run() which ends up calling run(flush()) of doProcess and thus executing process(chunk), where chunk is the flushed data of the arguments ArrayList. I skipped some details, the chain of "run" calls is like this:

  • doSubmit.run()
  • doSubmit.run(flush()) //Actually a loop of runnables but will only have one (*)
  • doProcess.run()
  • doProcess.run(flush())
  • process(chunk)

(*)The boolean isSubmited and flush() (which resets this boolean) make it so additional calls to publish don't add doProcess runnables to be called in doSubmit.run(flush()) however their data is not ignored. Thus executing a single process for any amount of publishes called during the life of a Timer.

All in all, what publish("Writing...") does is scheduling the call to process(chunk) in the EDT after a DELAY. This explains why even after we cancelled the thread and no more publishes are done, still one process execution appears, because the moment we cancel the worker there's (with high probability) a Timer that will schedule a process() after done() is already scheduled.

Why is this Timer used instead of just scheduling process() in the EDT with an invokeLater(doProcess)? To implement the performance optimization explained in the docs:

Because the process method is invoked asynchronously on the Event
Dispatch Thread multiple invocations to the publish method might occur
before the process method is executed. For performance purposes all
these invocations are coalesced into one invocation with concatenated
arguments.
For example:

 publish("1");
publish("2", "3");
publish("4", "5", "6");

might result in:
process("1", "2", "3", "4", "5", "6")

We now know that this works because all the publishes that occur within a DELAY interval are adding their args into that internal variable we saw arguments and the process(chunk) will execute with all that data in one go.

IS THIS A BUG? WORKAROUND?

It's hard to tell If this is a bug or not, It might make sense to process the data that the background thread has published, since the work is actually done and you might be interested in getting the GUI updated with as much info as you can (if that's what process() is doing, for example). And then it might not make sense if done() requires to have all the data processed and/or a call to process() after done() creates data/GUI inconsistencies.

There's an obvious workaround if you don't want any new process() to be executed after done(), simply check if the worker is cancelled in the process method too!

@Override
protected void process(List<String> chunks) {
if (isCancelled()) return;
String string = chunks.get(chunks.size() - 1);
textArea.append(string);
}

It's more tricky to make done() be executed after that last process(), for example done could just use also a timer that will schedule the actual done() work after >DELAY. Although I can't think this is would be a common case since if you cancelled It shouldn't be important to miss one more process() when we know that we are in fact cancelling the execution of all the future ones.

Pause and Resume SwingWorker.doInBackground()

Pause and Resume SwingWorker.doInBackground()

First of all you have to be sure the background task being performed can be paused, otherwise the question doesn't make sense. So let's say the task can be paused, then you might extend SwingWorker class and make your own pausable worker using a simple flag variable to control the background thread status: paused or not paused.

public abstract class PausableSwingWorker<K, V> extends SwingWorker<K, V> {

private volatile boolean isPaused;

public final void pause() {
if (!isPaused() && !isDone()) {
isPaused = true;
firePropertyChange("paused", false, true);
}
}

public final void resume() {
if (isPaused() && !isDone()) {
isPaused = false;
firePropertyChange("paused", true, false);
}
}

public final boolean isPaused() {
return isPaused;
}
}

Subclasses might check isPaused() status in order to efectively proceed with the task or not. For example:

PausableSwingWorker<Void, String> worker = new PausableSwingWorker<Void, String>() {
@Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
if (!isPaused()) {
// proceed with background task
} else {
Thread.sleep(200); // Optional sleep to avoid check status continuously
}
}
return null;
}
};

You can also add a PropertyChangeListener to the worker and listen for paused property changes:

worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("paused".equals(evt.getPropertyName())) {
System.out.println("Old status: " + evt.getOldValue());
System.out.println("New status: " + evt.getNewValue());
}
}
});

Example (updated to make use of PropertyChangeListener)

Here is a complete example to play with. Please note that if worker is stopped then it cannot be paused nor resumed anymore.

import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;

public class Demo {

private void createAndShowGUI() {

final JTextArea textArea = new JTextArea(20, 50);

final PausableSwingWorker<Void, String> worker = new PausableSwingWorker<Void, String>() {
@Override
protected Void doInBackground() throws Exception {
while (!isCancelled()) {
if (!isPaused()) {
publish("Writing...");
} else {
Thread.sleep(200);
}
}
return null;
}

@Override
protected void process(List<String> chunks) {
String text = String.format("%s%n", chunks.get(chunks.size() - 1));
textArea.append(text);
}
};

worker.addPropertyChangeListener(new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ("paused".equals(evt.getPropertyName())) {
String text = (Boolean)evt.getNewValue() ? "Paused..." : "Resumed...";
textArea.append(String.format("%s%n", text));
}
}
});

Action pause = new AbstractAction("Pause") {
@Override
public void actionPerformed(ActionEvent e) {
worker.pause();
}
};

Action resume = new AbstractAction("Resume") {
@Override
public void actionPerformed(ActionEvent e) {
worker.resume();
}
};

Action stop = new AbstractAction("Stop") {
@Override
public void actionPerformed(ActionEvent e) {
worker.cancel(true);
}
};

JPanel buttonsPanel = new JPanel(new FlowLayout(FlowLayout.CENTER));
buttonsPanel.add(new JButton(pause));
buttonsPanel.add(new JButton(resume));
buttonsPanel.add(new JButton(stop));

JPanel content = new JPanel(new BorderLayout(8, 8));
content.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
content.add(new JScrollPane(textArea), BorderLayout.CENTER);
content.add(buttonsPanel, BorderLayout.SOUTH);

JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);

frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
if (!worker.isDone()) {
worker.cancel(true);
}
e.getWindow().dispose();
}
});

frame.add(content);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);

worker.execute();
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Demo().createAndShowGUI();
}
});
}

abstract class PausableSwingWorker<K, V> extends SwingWorker<K, V> {

private volatile boolean isPaused;

public final void pause() {
if (!isPaused() && !isDone()) {
isPaused = true;
firePropertyChange("paused", false, true);
}
}

public final void resume() {
if (isPaused() && !isDone()) {
isPaused = false;
firePropertyChange("paused", true, false);
}
}

public final boolean isPaused() {
return isPaused;
}
}
}

java swingworker thread to update main Gui

There is an excellent example from the JavaDocs

class PrimeNumbersTask extends
SwingWorker<List<Integer>, Integer> {

PrimeNumbersTask(JTextArea textArea, int numbersToFind) {
//initialize
}

@Override
public List<Integer> doInBackground() {
List<Integer> numbers = new ArrayList<Integer>(25);
while (!enough && !isCancelled()) {
number = nextPrimeNumber();
numbers.add(number);
publish(number);
setProgress(100 * numbers.size() / numbersToFind);
}

return numbers;
}

@Override
protected void process(List<Integer> chunks) {
for (int number : chunks) {
textArea.append(number + "\n");
}
}
}

JTextArea textArea = new JTextArea();
final JProgressBar progressBar = new JProgressBar(0, 100);
PrimeNumbersTask task = new PrimeNumbersTask(textArea, N);
task.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});

task.execute();
System.out.println(task.get()); //prints all prime numbers we have got

Take a look at publish and process

The underlying intention is that you need to update the UI from only within the Event Dispatching Thread, by passing the data you want to updated to the UI via the publish method, SwingWorker will call process for you within the context of the EDT

JavaFX SwingWorker Equivalent?

I would rewrite your SwingWorker as follows:

class PrimeNumbersTask extends Task<List<Integer>> {
PrimeNumbersTask(TextArea textArea, int numbersToFind) {
// initialize
}

@Override
protected List<Integer> call() throws Exception {
while (!enough && !isCancelled()) {
number = nextPrimeNumber();
updateMessage(Integer.toString(number));
updateProgress(numbers.size(), numbersToFind);
}
return numbers;
}
}

Usage:

TextArea textArea = new TextArea();
PrimeNumbersTask task = new PrimeNumbersTask(numbersToFind);
task.messageProperty().addListener((w, o, n)->textArea.appendText(n + "\n"));
new Thread(task).start(); // which would actually start task on a new thread

Explanation:

Yes, we do not have a publish() method as the SwingWorker does in JavaFX, but in your case using the updateMessage() is sufficient, as we can register a listener to this property and append a new line every time the message is updated.

If this is not enough, you can always use Platform.runLater() to schedule GUI updates. If you are doing too many GUI updates and the GUI Thread is being slowed down, you can use the following idiom: Throttling javafx gui updates

How to use SwingWorker to update the GUI in real-time?

your background threads should call method

String message="hi im processx";
SwingWorker.publish(message);

and you have to ovveride method

SwingWokrer.process().

this method is called on EDT when it is free with the message which was passed before to publish (might be one or more) in the argument.

You should display your progress there

Yo can read more about those methods in SwingWorker documentation



Related Topics



Leave a reply



Submit