How do I simulate a buffered peripheral device with SwingWorker?
There was one thing missing from the "answer" I had appended to the original question:
I was handing off the time-consuming work (nothing more than a Thread.sleep()
for pedagogical purposes) to a background thread, via a Single Thread Executor. A problem arose, however, because the background thread was "reading a card" by poll()ing the List that was serving as the data model for a Swing component, and raising lots of AWT array index out of range exceptions. After several futile attempts to synchronize access to the List by both the EDT and my background thread, I punted, and wrapped the commands to poll() the List and update the GUI in a small Runnable(), and used invokeAndWait() to cause them to run on the EDT while my background task waited.
Here's my revised solution:
private ExecutorService executorService;
:
executorService = Executors.newSingleThreadExecutor();
:
/*
* When EDT receives a request for a card it calls readCard(),
* which queues the work to the *single* thread.
*/
public void readCard() throws Exception {
executorService.execute(new Runnable() {
public void run() {
if (buffer.isEmpty()) {
/*
* fill() takes 1/4 second (simulated by Thread.sleep)
*/
buffer.fill();
}
Card card = buffer.get(); // empties buffer
/*
* Send card to CPU
*/
CPU.sendMessage(card); // <== (A) put card in msg queue
/*
* No race! Next request will run on same thread, after us.
*/
buffer.fill(); // <== (B) pre-fetch next card
return;
}
});
}
/*
* IMPORTANT MODIFICATION HERE - - -
*
* buffer fill() method has to remove item from the list that is the
* model behind a JList - only safe way is to do that on EDT!
*/
private void fill() {
SwingUtilities.invokeAndWait(new Runnable() {
/*
* Running here on the EDT
*/
public void run() {
/*
* Hopper not empty, so we will be able to read a card.
*/
buffer = readHopper.pollLast(); // read next card from current deck
fireIntervalRemoved(this, readHopper.size(), readHopper.size());
gui.viewBottomOfHopper(); // scroll read hopper view correctly
}
});
// back to my worker thread, to do 1/4 sec. of heavy number crunching ;)
// while leaving the GUI responsive
Thread.sleep(250);
:
etc.
}
How to make a SwingWorker Start more than once..?
You can't restart a SwingWorker, and what's more, you don't want to. Per the SwingWorker API:
SwingWorker is only designed to be executed once. Executing a SwingWorker more than once will not result in invoking the doInBackground method twice.
Your solution is to create a new SwingWorker instance within your button's ActionListener and execute it; do this, and you'll likely be fine.
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.
cleaning up after using a SwingWorker
Disclaimer: I'm not sure I see the need to do this, but...
Having dug through the SwingWorker
source code, the worker uses the private
getWorkersExecutorService
method to get the ExecutorService
for the current JVM.
This uses the AppContext
to store an instance of ExecutorService
, from which all instances of SwingWorker
(for the JVM) will draw threads from to execute their functionality.
So, if you do something like...
AppContext appContext = AppContext.getAppContext();
ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
System.out.println(executorService);
will print out something like...
java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
(this assumes you've previously started a SwingWorker
otherwise it will return null
)
But, what does this mean? Well, we can now gain direct access the ExecutorService
for all the SwingWorker
s (the apocalypse may now start)
This means you can shutdown the service and even remove it from the AppContext
, for example...
SwingWorker worker = new SwingWorker() {
@Override
protected Object doInBackground() throws Exception {
System.out.println("Starting");
Thread.sleep(10000);
System.out.println("Ending");
return null;
}
};
worker.execute();
synchronized (SwingWorker.class) {
AppContext appContext = AppContext.getAppContext();
ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
System.out.println(executorService);
System.out.println("Shutting down");
executorService.shutdownNow();
try {
System.out.println("Waiting");
executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
System.out.println("Done");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
appContext.remove(SwingWorker.class);
}
In my testing, it outputs...
java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
Shutting down
Waiting
Starting
Done
So the worker doesn't even get a chance to start. If I place a delay in (after calling execute
), shutdownNow
basically hammers it and won't wait for the worker to finish, but if I use shutdown
it does, so, there you have it.
I should add, AFAIK WAITING
means they are doing nothing. In the case of the SwingWorker
, these threads are pooled, so, yes, the worker will try and re-use them if it can, BUT because in the "normal" operations of the SwingWorker
, the worker needs to deal with the fact that the doInBackground
method might explode horrible, it's already designed to deal with it (which is why get
throws an Exception
), so unless you have a particular use case, I'm not really sure you need to go to this much trouble, just saying
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();
}
}
}
cleaning up after using a SwingWorker
Disclaimer: I'm not sure I see the need to do this, but...
Having dug through the SwingWorker
source code, the worker uses the private
getWorkersExecutorService
method to get the ExecutorService
for the current JVM.
This uses the AppContext
to store an instance of ExecutorService
, from which all instances of SwingWorker
(for the JVM) will draw threads from to execute their functionality.
So, if you do something like...
AppContext appContext = AppContext.getAppContext();
ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
System.out.println(executorService);
will print out something like...
java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
(this assumes you've previously started a SwingWorker
otherwise it will return null
)
But, what does this mean? Well, we can now gain direct access the ExecutorService
for all the SwingWorker
s (the apocalypse may now start)
This means you can shutdown the service and even remove it from the AppContext
, for example...
SwingWorker worker = new SwingWorker() {
@Override
protected Object doInBackground() throws Exception {
System.out.println("Starting");
Thread.sleep(10000);
System.out.println("Ending");
return null;
}
};
worker.execute();
synchronized (SwingWorker.class) {
AppContext appContext = AppContext.getAppContext();
ExecutorService executorService = (ExecutorService) appContext.get(SwingWorker.class);
System.out.println(executorService);
System.out.println("Shutting down");
executorService.shutdownNow();
try {
System.out.println("Waiting");
executorService.awaitTermination(Integer.MAX_VALUE, TimeUnit.DAYS);
System.out.println("Done");
} catch (InterruptedException ex) {
ex.printStackTrace();
}
appContext.remove(SwingWorker.class);
}
In my testing, it outputs...
java.util.concurrent.ThreadPoolExecutor@4554617c[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
Shutting down
Waiting
Starting
Done
So the worker doesn't even get a chance to start. If I place a delay in (after calling execute
), shutdownNow
basically hammers it and won't wait for the worker to finish, but if I use shutdown
it does, so, there you have it.
I should add, AFAIK WAITING
means they are doing nothing. In the case of the SwingWorker
, these threads are pooled, so, yes, the worker will try and re-use them if it can, BUT because in the "normal" operations of the SwingWorker
, the worker needs to deal with the fact that the doInBackground
method might explode horrible, it's already designed to deal with it (which is why get
throws an Exception
), so unless you have a particular use case, I'm not really sure you need to go to this much trouble, just saying
Showing GlassPane before entering loop
GUI just freezes for a moment, before result is displayed. Removing last line in that loop (glassPane.setVisible(false);) results in showing glassPane after the method is done (when GUI unfreezes).
this is common issue about Event Dispath Thread
, when all events in EDT
are flushed to the Swing GUI
in one moment, then everything in the method if (item.equals(button)) {
could be done on one moment,
but your description talking you have got issue with Concurency in Swing, some of code blocking EDT, this is small delay, for example Thread.sleep(int
) can caused this issue, don't do that, or redirect code block to the Backgroung taks
Is there a simple way to show that glassPane before GUI freezes, or I need to use some advanced knowledge here? (threads?)
this question is booking example why SwingWorker
is there, or easier way is Runnable#Thread
methods implemented in
SwingWorker
quite guarante that output will be done on EDTany output from
Runnable#Thread
to the Swing GUI should be wrapped ininvokeLater()
easiest steps from Jbuttons Action
could be
show
GlassPane
start background task from
SwingWorker
(be sure that listening byPropertyChangeListener
) or invokeRunnable#Thread
in this moment
ActionListener
executions is done rest of code is redirected to the Backgroung taksif task ended, then to hide
GlassPane
create simple void by wrapping
setVisible
intoinvokeLater()
forRunnable#Thread
in the case that you use
SwingWorker
then you can to hide theGlassPane
on proper event fromPropertyChangeListener
or you can to use any (separate) void for hidding theGlassPane
best code for GlassPane by @camickr, or my question about based on this code
Related Topics
Converting an Array of Objects to an Array of Their Primitive Types
Issue Using Imageio.Write Jpg File: Pink Background
What Is the Purpose of @Namedarg Annotation in Javafx 8
Java 8: Difference Between Method Reference Bound Receiver and Unbound Receiver
"Unable to Acquire Application Service" Error While Launching Eclipse
How to Pass Arithmetic Operators to a Method in Java
Java Inetaddress.Getlocalhost(); Returns 127.0.0.1 ... How to Get Real Ip
How to Obtain Mouse Click Coordinates Outside My Window in Java
How to Calculate a Time Span in Java and Format the Output
How to Retrieve Value from Jtextfield in Java Swing
Swing on Osx: How to Trap Command-Q
Parsing Xml with Regex in Java
Garbage Collector in Java - Set an Object Null