How to Simulate a Buffered Peripheral Device with Swingworker

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 SwingWorkers (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.

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

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 SwingWorkers (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 EDT

  • any output from Runnable#Thread to the Swing GUI should be wrapped in invokeLater()

easiest steps from Jbuttons Action could be

  • show GlassPane

  • start background task from SwingWorker (be sure that listening by PropertyChangeListener) or invoke Runnable#Thread

  • in this moment ActionListener executions is done rest of code is redirected to the Backgroung taks

  • if task ended, then to hide GlassPane

  • create simple void by wrapping setVisible into invokeLater() for Runnable#Thread

  • in the case that you use SwingWorker then you can to hide the GlassPane on proper event from PropertyChangeListener or you can to use any (separate) void for hidding the GlassPane

best code for GlassPane by @camickr, or my question about based on this code



Related Topics



Leave a reply



Submit