Waiting For Multiple Swingworkers

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();
}
}
}

Waiting for a SwingWorker to finish before executing another

Once I've done an OrderedResultsExecutors that maintains the order of adding tasks with the order of notifying about results. All you have to do is to implement the notify method for your case eg. write some Listener or something. Of course you could pass a collection of Report to the SwingWorker and process them in a for loop, but in that case you'll lose multithreading, and all the tasks could take considerably more time to execute in such single-threaded manner. That's why it could be better to have a rally multithreaded version of such mechanism, like this:

Import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

public class OrderedResultsExecutors extends ThreadPoolExecutor {
public OrderedResultsExecutors(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}

private ConcurrentHashMap<Long, Runnable> startedTasks = new ConcurrentHashMap<>();
private ConcurrentLinkedDeque<Runnable> finishedTasks = new ConcurrentLinkedDeque<>();
private AtomicLong toNotify = new AtomicLong(0);
private AtomicLong submitedCount = new AtomicLong(0);

@Override
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
startedTasks.put(submitedCount.getAndIncrement(), r);
}

@Override
protected void afterExecute(Runnable r, Throwable t) {
super.afterExecute(r, t);
finishedTasks.add(r);
finishedTask();
}

private void finishedTask() {
Runnable orderedResult;
long current;
while ((orderedResult = startedTasks.get(current = toNotify.get())) != null
&& finishedTasks.contains(orderedResult) && (orderedResult = startedTasks.remove(current)) != null) {
finishedTasks.remove(orderedResult);
notify(current, orderedResult);
toNotify.incrementAndGet();
}
}

private void notify(long order, Runnable result) {
try {
System.out.println("order: " + order + " result: " + ((Future)result).get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}

public static ExecutorService newFixedThreadPool(int noOfThreads) {
int corePoolSize = noOfThreads;
int maximumPoolSize = noOfThreads;
return new OrderedResultsExecutors(corePoolSize, maximumPoolSize, 0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}

}

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.

How to make sure the done() method of two concurrent swingworkers is executed in EDT in the right order?

If you are OK with the workers running serially, which they will if you synchronize on a lock per your sample code, and you are not using any of the other features of SwingWorker other than doInBackground() and done(), it may be easier for you to use a single thread executor and SwingUtilities.invokeLater(...) to present the results on the GUI.

This way, you are guaranteed that things submitted to the executor will be run in the order they are submitted.

If this works for you, you could try something like this:

final ExecutorService executor = Executors.newSingleThreadExecutor();

// Code that runs in the first button
Runnable worker1 = new Runnable() {
@Override
public void run() {
System.out.println("Thread 1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace(System.err);
}
SwingUtilities.invokeLater(this::updateGUI);
}

private void updateGUI() {
System.out.println("Done with thread 1");
//update GUI
}
};
executor.execute(worker1);

// Code that runs in the second button
Runnable worker2 = new Runnable() {
@Override
public void run() {
System.out.println("Thread 2");
SwingUtilities.invokeLater(this::updateGUI);
}

private void updateGUI() {
System.out.println("Done with thread 2");
//update GUI
}
};
executor.execute(worker2);

How to wait until a SwingWorker is complete?

The short answer is, yes.

SwingWorker#get will block until the doInBackground method returns.

The long answer is, probably not what you want to do.

If you launch the SwingWorker from the EDT and the call SwingWorker#get you will be block the EDT, preventing it from processing any updates which leads you back to the reason why you're using SwingWorker.

In this case, you have two choices. You could provide SwingWorker with a callback interface, which it would be able to call from done once the SwingWorker has completed. Or you could attach a PropertyChangeListener to the SwingWorker and monitor the state value, waiting until it equals StateValue.Done

How to signal that multiple SwingWorkers are all done?

If you know how many workers you're kicking off, you can use a CountdownLatch. If you don't know how many works are being kicked off, you can use a Phaser.

Example:

//using a button as a basic UI component to do work.
JButton button = new JButton(new AbstractAction() {

@Override
public void actionPerformed(ActionEvent e) {
Runnable control = new Runnable() {
@Override
public void run() {
//assuming we know we're going to do 20 bits of isolated work.
final CountDownLatch latch = new CountDownLatch(20);
for (int i = 0; i < 20; i++) {
SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
@Override
public Void doInBackground() {
//do your work
return null;
}

@Override
public void done() {
latch.countDown();
}
};
worker.run()
}
try {
latch.await();
} catch (Exception e) {
e.printStackTrace();
}
//so your next bit of work.
}
};
SwingUtilities.invokeLater(control);
}
});

Is it possible for a Java SwingWorker doInBackground() to wait for an Interim Process to be completed before continuing

Read the section from the Swing tutorial on Tasks That Have Interim Results. You will find a statement that says:

Results from multiple invocations of publish are often accumulated for a single invocation of process.

Object webscraingData = chunks.get(0);

You only process a single item.

You need to iterate through all the items in the list.

How do I wait for a SwingWorker's doInBackground() method?

Typically anything that needs to be done after a SwingWorker completes its background work is done by overriding the done() method in it. This method is called on the Swing event thread after completion, allowing you to update the GUI or print something out or whatever. If you really do need to block until it completes, you can call get().

NB. Calling get() within the done() method will return with your result immediately, so you don't have to worry about that blocking any UI work.



Related Topics



Leave a reply



Submit