Managing updates from other threads into Swing is a hard problem. hard problem. In MVC design, if you don't want to have the Presenter be responsible for Thread safety, you can end up with deadlock issues, and also too many little tasks getting started; not great.
Managing updates from other threads into Swing is a hard problem. In MVC design, if you don't want to have the Presenter be responsible for Thread safety, you can end up with deadlock issues, and also too many little tasks getting started; not great.
Managing updates from other threads into Swing is a hard problem. In MVC design, if you don't want to have the Presenter be responsible for Thread safety, you can end up with deadlock issues, and also too many little tasks getting started; not great.
Below, you will also find a harness that uses this class the way it's intended, to create a complete, working program. All feedback is welcome, but mostly I'm looking for thoughts on this SwingUpdater
class. Note: this is Java 7 code, please no feedback that involves lambdas or method references.
Below, you will also find a harness that uses this class the way it's intended, to create a complete, working program. All feedback is welcome, but mostly I'm looking for thoughts on this SwingUpdater
class.
Below, you will also find a harness that uses this class the way it's intended, to create a complete, working program. All feedback is welcome, but mostly I'm looking for thoughts on this SwingUpdater
class. Note: this is Java 7 code, please no feedback that involves lambdas or method references.
Class to manage updates coming in from another thread
Managing updates from other threads into Swing is a hard problem. In MVC design, if you don't want to have the Presenter be responsible for Thread safety, you can end up with deadlock issues, and also too many little tasks getting started; not great.
I have written a class designed to manage these updates in the view so that the Presenter is not coupled with the View's threading model. It loads them all into a queue, and then, if a new update is added, it processes the entire queue in case too many updates have come too quickly. Here's the class that I'm looking for feedback on.
Below, you will also find a harness that uses this class the way it's intended, to create a complete, working program. All feedback is welcome, but mostly I'm looking for thoughts on this SwingUpdater
class.
import java.awt.EventQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class SwingUpdater<E> implements Runnable {
private static final Logger LOG =
LoggerFactory.getLogger(SwingUpdater.class);
private LinkedBlockingQueue<E> updates;
private AtomicBoolean updating = new AtomicBoolean(false);
public SwingUpdater() {
updates = new LinkedBlockingQueue<>();
}
public final void setObject(E object) {
updates.add(object);
if(!updating.getAndSet(true)) EventQueue.invokeLater(this);
}
@Override
public void run() {
while(updates.size() > 0) {
try {
doTask(updates.take());
} catch (InterruptedException e) {
LOG.warn("Interrupted while trying to process updates. Remaining updates: {}", updates.size());
break;
}
}
updating.set(false);
}
protected abstract void doTask(E update);
}
This class uses SwingUpdater
as it's intended to be used; I'm sure this class could be better written (and more than one class) but I threw it together quite quickly to be able to provide a complete working program with a main
method and so forth for SwingUpdater
.
Here it is:
import java.awt.*;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.swing.*;
public class SwingUpdaterExample {
private final JFrame testFrame;
private final JPanel contentPane;
private final JTextPane textPane;
private int updateCount;
private final SwingUpdater<String> updater = new SwingUpdater<String>() {
@Override
protected void doTask(String update) {
String oldText = textPane.getText();
textPane.setText(oldText + ((updateCount & 3) == 0 ? System.lineSeparator() : "\t") + update);
updateCount++;
}
};
public SwingUpdaterExample() {
textPane = new JTextPane();
contentPane = new JPanel(new BorderLayout());
contentPane.add(textPane);
testFrame = new JFrame("ExampleFrame");
testFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
testFrame.setSize(500,800);
testFrame.setContentPane(contentPane);
}
public void setVisible(boolean visible) {
testFrame.setVisible(true);
}
public void appendText(String string) {
updater.setObject(string);
}
public static class ExampleTask implements Runnable {
private final String name;
private final SwingUpdaterExample view;
public ExampleTask(String name, SwingUpdaterExample view) {
this.name = name;
this.view = view;
}
@Override
public void run() {
Random r = new Random();
for(int counter = 0; counter < 15; counter++) {
if(r.nextDouble() < 0.3) {
try {
Thread.sleep(2000 + r.nextInt(4000));
} catch (InterruptedException e) {
break;
}
}
view.appendText(name + " - " + counter);
}
}
}
public static void main(String[] args) throws Exception {
final SwingUpdaterExample frame = new SwingUpdaterExample();
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
frame.setVisible(true);
}
});
ExecutorService service = Executors.newCachedThreadPool();
for(int i = 0; i < 4; i++) {
ExampleTask task = new ExampleTask("Task" + i, frame);
service.submit(task);
}
service.shutdown();
}
}