Multiple SwingWorkers with ExecuterService does not work properly
- SwingWorker uses it's own ThreadPool.
- 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.
- 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
.
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 viaSwingWorker
mechanism. - I believe that the instantiation of the
ExecutorService
is hidden as it is part ofSwingWorker
implementation and you may not edit it. - But at the same time, since
SwingWorker
implementsRunnable
, you can submit it to your ownExecutorService
instance. - If you call
execute()
method, then it will be scheduled to it's ownExecutorService
. Instead, you can manually submit theSwingWorker
instance to your ownExecutorService
.
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
Why Doesn't Java Offer Operator Overloading
Can Enums Be Subclassed to Add New Elements
Most Simple Code to Populate Jtable from Resultset
Javafx Fxml Controller - Constructor VS Initialize Method
Cut Out Image in Shape of Text
Which Overload Will Get Selected for Null in Java
How to Configure Port for a Spring Boot Application
Why Should Java 8's Optional Not Be Used in Arguments
Remove Diacritical Marks (Ń Ǹ Ň ñ Ṅ Ņ Ṇ Ṋ Ṉ ̈ Ɲ Ƞ ᶇ ɳ ȵ) from Unicode Chars
Under What Conditions Is a Jsessionid Created
Closing Database Connections in Java