Swing thread safety boilerplate
For the sake of simplicity, imagine an application that downloads a file. There is a simple GUI with one label that displays progress. To avoid EDT violations, like every lawful citizen I download the file in one thread (main), and update GUI in another (EDT). So, here's the relevant chunk of pseudcode:
class Downloader {
download() {
progress.startDownload();
while(hasMoreChunks()) {
downloadChunk();
progress.downloadedBytes(n);
}
progress.finishDownload();
}
}
class ProgressDisplay extends JPanel {
JLabel label;
startDownload() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText("Download started");
}
});
}
downloadedBytes(int n) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText("Downloaded bytes: " + n);
}
});
}
finishDownload() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText("Download finished");
}
});
}
}
I love the fact that Java does not support closures and the code is crystal clear to me. Jokes aside, I'm wondering... Am I doing it wrong? Is it possible to eliminate all this ugly boilerplate with SwingUtilities
, anonymous implementation of Runnable
in every method etc.?
My case is slightly more complicated than this, but I'm trying not to overengineer by implementing proxies or something like that.
There is special class for that tasks: SwingWorker
. It allows you yo run code in a separate thread and update UI on the end of the work.
There is not much you can do to avoid the boilerplate code without introducing a lot of redundant code elsewhere. But you can make it a little bit nicer with a small abstract helper class and some unusual formatting.
public abstract static class SwingTask implements Runnable
{
public void start()
{
SwingUtilities.invokeLater( this );
}
}
startDownload() {
new SwingTask() { public void run() {
label.setText("Download started");
} }.start();
}
The code formatting is meant to emphasize that all the code in this single line is basically uninteresting boilerplate code that can easily and safely be copied to other places where it is needed. We have been using this formatting style now for a while and it turned out quite useful.
I would use a helper method like.
public void setLabelText(final JLabel label, final String text) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
label.setText(text);
}
});
}
We used Spin in a project a few years back.
Just a raw idea:
public class ThreadSafeJLabel extends JLabel {
@Override
public void setText(final String text) {
if (SwingUtilities.isEventDispatchThread()) {
super.setText(text);
} else {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
setText(text);
}
});
}
}
}
Haven't tried it but I guess when called setText()
from EDT, super
will be used; but when other thread is involved, the overridden (ThreadSafeJLabel.setText()
) will be called later, this time inside EDT.
精彩评论