How to Share Data with Two(2) Swingworker Class in Java

Multiple SwingWorkers with ExecuterService does not work properly

  1. SwingWorker uses it's own ThreadPool.
  2. SwingWorker should be used for a long running task after (i.e. anything that required more than a couple of milliseconds) after which a GUI update is required.
  3. No calls to update gui elements outside the EDT should be done (i.e. from the SwingWorker.done() method)

If you generally follow the rules of accessing Swing components form inside the EDT (look here) then you shouldn't have a problem with locking. I suspect that the problem rather lies in your code but to be sure of that we should see it.

Waiting for multiple SwingWorkers

I intend to remove all of the labels together when all of the workers have completed their tasks.

As described here, a CountDownLatch works well in this context. In the example below, each worker invokes latch.countDown() on completion, and a Supervisor worker blocks on latch.await() until all tasks complete. For demonstration purposes, the Supervisor updates the labels. Wholesale removal, shown in comments, is technically possible but generally unappealing. Instead, consider a JList or JTable.

Worker Latch Test

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.*;

/**
* @see https://stackoverflow.com/a/11372932/230513
* @see https://stackoverflow.com/a/3588523/230513
*/
public class WorkerLatchTest extends JApplet {

private static final int N = 8;
private static final Random rand = new Random();
private Queue<JLabel> labels = new LinkedList<JLabel>();
private JPanel panel = new JPanel(new GridLayout(0, 1));
private JButton startButton = new JButton(new StartAction("Do work"));

public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
JFrame frame = new JFrame();
frame.setTitle("Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new WorkerLatchTest().createGUI());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}

@Override
public void init() {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
add(new WorkerLatchTest().createGUI());
}
});
}

private JPanel createGUI() {
for (int i = 0; i < N; i++) {
JLabel label = new JLabel("0", JLabel.CENTER);
label.setOpaque(true);
panel.add(label);
labels.add(label);
}
panel.add(startButton);
return panel;
}

private class StartAction extends AbstractAction {

private StartAction(String name) {
super(name);
}

@Override
public void actionPerformed(ActionEvent e) {
startButton.setEnabled(false);
CountDownLatch latch = new CountDownLatch(N);
ExecutorService executor = Executors.newFixedThreadPool(N);
for (JLabel label : labels) {
label.setBackground(Color.white);
executor.execute(new Counter(label, latch));
}
new Supervisor(latch).execute();
}
}

private class Supervisor extends SwingWorker<Void, Void> {

CountDownLatch latch;

public Supervisor(CountDownLatch latch) {
this.latch = latch;
}

@Override
protected Void doInBackground() throws Exception {
latch.await();
return null;
}

@Override
protected void done() {
for (JLabel label : labels) {
label.setText("Fin!");
label.setBackground(Color.lightGray);
}
startButton.setEnabled(true);
//panel.removeAll(); panel.revalidate(); panel.repaint();
}
}

private static class Counter extends SwingWorker<Void, Integer> {

private JLabel label;
CountDownLatch latch;

public Counter(JLabel label, CountDownLatch latch) {
this.label = label;
this.latch = latch;
}

@Override
protected Void doInBackground() throws Exception {
int latency = rand.nextInt(42) + 10;
for (int i = 1; i <= 100; i++) {
publish(i);
Thread.sleep(latency);
}
return null;
}

@Override
protected void process(List<Integer> values) {
label.setText(values.get(values.size() - 1).toString());
}

@Override
protected void done() {
label.setBackground(Color.green);
latch.countDown();
}
}
}

SwingWorker for Both Output & Work, or 1 Each?

If you are using JDK7, you might consider using SecondaryLoop instead of chaining together multiple SwingWorker instances.

SecondaryLoop essentially allows you to wait for a background thread (or series of threads) to complete without blocking the EDT, similar to SwingWorker. It is may not be suitable as a replacement for SwingWorker in every case, however based on the description of your problem it sounds like this is something you might be able to use.

For more discussion on this topic, please take a look related question, "SecondaryLoop instead of SwingWorker?
"

how to update GUI from swingworker which returns two different values

The second type parameter V in SwingWorker<T,V> is used for carrying out intermediate results by this SwingWorker's publish and process methods. This could be your custom class. Here is an example based on posted SSCCE (shortened for clarity):

class Progress {
private int task;
private int element;

public Progress(int task, int element) {
super();
this.task = task;
this.element = element;
}
...
}

public class Model extends SwingWorker<Integer, Progress> {
...
@Override
protected Integer doInBackground() throws Exception {
...
publish(new Progress(i, ii));
}
}

EDIT: example of process method implementation

@Override
protected void process(List<Progress> progressList) {
for (Progress p : progressList){
System.out.println(p.getTask() + " : " + p.getElement());
}
}

EDIT: example of UI update

Here is a slightly modified version of the worker implementation, similar to a sample demonstrated in SwingWorker manual. The only changes are introduction of textArea member and updated setProgress() call in doInBackground(). progress property is used to update the progress bar, process() is used to update text area.

public static class Model extends SwingWorker<Integer, Progress> {

private HashMap<String, Number> GUIparams;
private int session;
private int ticks;
private JTextArea textArea;

Model(HashMap<String, Number> KSMParams, JTextArea textArea) {
GUIparams = KSMParams;
session = (Integer)GUIparams.get("experimentsInSession");
ticks = (Integer)GUIparams.get("howManyTicks");

this.textArea = textArea;
}

@Override
protected void process(List<Progress> progressList) {
for (Progress p : progressList){
textArea.append(p.getTask() + " : " + p.getElement() + "\n");
System.out.println(p.getTask() + " : " + p.getElement());
}
}

/**
* Actual simulation
*/
@Override
protected Integer doInBackground() throws Exception {

int i=0;
while(!isCancelled() && i<session){
i++;
int ii=0;
while(!isCancelled() && ii<ticks){
// this is n, the task length and I'd like to update the GUI with this value
ii++;
}
//System.out.println(i);
// this is m, how many time the task has been repeated, and now it is updated in the GUI
publish(new Progress(i, ii));
//setProgress(i);
setProgress(100 * i / session);
Thread.sleep(1000);
}
return i;
}

/**
* Invoked when simulation exits
*/
@Override
protected void done() {
if (isCancelled()) {
Logger.getLogger(Model.class.getName()).log(Level.WARNING, "Experiment session cancelled by user. Closing Session...");
} else {
// do stuff

Logger.getLogger(Model.class.getName()).log(Level.WARNING, "Experiment session ended.");
}
}
}

Here is a demo initialization:

final JProgressBar progressBar = new JProgressBar(0, 100);
final JTextArea textArea = new JTextArea();
final JButton button = new JButton("Start");

button.addActionListener(new ActionListener(){
@Override
public void actionPerformed(ActionEvent e) {
HashMap<String, Number> map = Maps.newHashMap();
map.put("experimentsInSession", 10);
map.put("howManyTicks", 5);

Model task = new Model(map, textArea);
task.addPropertyChangeListener(
new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equals(evt.getPropertyName())) {
progressBar.setValue((Integer)evt.getNewValue());
}
}
});
task.execute();
}
});

ExecutorService and SwingWorker

How does SwingWorker work with ExecutorService?

  • Swing maintains an internal ExecutorService instance which is used
    to execute tasks that you submit via SwingWorker mechanism.
  • I believe that the instantiation of the ExecutorService is hidden as it is part of SwingWorker implementation and you may not edit it.
  • But at the same time, since SwingWorker implements Runnable, you can submit it to your own ExecutorService instance.
  • If you call execute() method, then it will be scheduled to it's own ExecutorService. Instead, you can manually submit the SwingWorker instance to your own ExecutorService.

If I submit a SwingWorker task to an Executor, will that one task spawn up to ten threads?

  • No. To understand this, you should go through ExecutorService
    documentation. One task will only use one thread (unless you
    specifically program multithreaded task).
  • Other threads are kept in idle by ExecutorService. This consumes almost no CPU time.
  • If 10 GUI events occur simultaneously, each will be assigned to each of the available thread. If the thread is not required, it will not be running.
  • If number of tasks is more than number of threads available, they will be scheduled in a queue by the ExecutorService itself.

Hope this clears the doubts. Usually it is a good idea to use default implementation (it works very well), but you easily can limit number of threads if you want. Also, it will not replace Swing's ExecutorService. Since it is already there, it's best to use it instead of creating another.

Good Luck.

ScheduledThreadPoolExecutor only runs Swingworker once

While SwingWorker does implement the Runnable interface, per its API section on the doInBackground() method:

Note that this method is executed only once.

So while its inner run() method may repeatedly run, the doInBackground() will only run once. Not only that, but the run() method is marked final within SwingWorker, and so you can't override it to call doInBackground multiple times.

A better solution is not use a SwingWorker at all but rather a simpler Runnable-derived class.

Swingworker instances not running concurrently

This is the result of a change in SwingWorker behaviour from one Java 6 update to another. There's actually a bug report about it in the old SUN bug database for Java 6.

What happened was SUN changed the original SwingWorker implementation from using N threads, to using 1 thread with an unbounded queue. Therefore you cannot have two SwingWorkers run concurrently anymore. (This also creates a scenario where deadlock may occur: if one swingworker waits for another which waits for the first, one task might enter a deadlock as a result.)

What you can do to get around this is use an ExecutorService and post FutureTasks on it. These will provide 99% of the SwingWorker API (SwingWorker is a FutureTask derivative), all you have to do is set up your Executor properly.

Differences between SwingWorker and Executor

1) SwingWorker is created as bridge betweens Java Essentials Classes and Swing by implements Future, and quite guarantee that outoput from methods process, publish and done will be on EventDispatchThread

2) you can invoke SwingWorker from Executor, this is most safiest methods how to create multithreading in Swing by implements SwingWorker

3) notice carefully with number or thread, because Executor doesn't care somehow about SwingWorkers life_cycle

4) another important notice and here

5) for listening states from SwingWorker you have to implements PropertyChangeListener



Related Topics



Leave a reply



Submit