2

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.

Konrad Garus
  • 53,145
  • 43
  • 157
  • 230
  • I do not think it is *that* ugly. Compared to a closure you have just the extra `new Runnable()` basically. – gpeche Jun 02 '11 at 15:15
  • Compared to this closure? `runInEdt({label.setText("Download started");})` Or even: `runInEdt(someBiggerAction)`, where `someBiggerAction` is method name. I know I'm tainted and it's in no way possible in Java, but I can't imagine after all those years everyone violates EDT or writes this 4-line wrapper 100 times. – Konrad Garus Jun 02 '11 at 15:22
  • As I said, the only difference is the `new Runnable` thing. You can have `runInEdt` now. In fact you already have, it is called `SwingUtilities.invokeLater`. Java is a bit wordy (by design). – gpeche Jun 02 '11 at 15:39

5 Answers5

2

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.

Vladimir Ivanov
  • 42,730
  • 18
  • 77
  • 103
  • Thank you, but please at least read the question. It's about eliminating boilerplate on updating Swing components from non-Swing thread, not "how to execute background tasks from Swing thread". – Konrad Garus Jun 02 '11 at 14:54
  • I agree with @Vladimir. First, the Swing components are not supposed to be thread safe, so it looks really weird to define methods on them that call the EDT. You should rather assume that the Swing component methods are always called from the EDT, as all Swing component methods. You should rethink you whole program design and you will probably end up using `SwingWorker`. – toto2 Jun 02 '11 at 16:13
  • That's besides the point. I am going to end up having this boilerplate somewhere. (Do I have to? That's what I'm asking here) – Konrad Garus Jun 03 '11 at 05:52
  • Doing this in-component is better for testability - you can define interface for `ProgressMonitor` and simply call `start()`, `showProgress()` and `stop()` without forcing the user (`DownloadManager`) to remember about EDT. Arguably, I would say this is a better API (and I think that's the case in my application). – Konrad Garus Jun 03 '11 at 05:53
  • Finally, you guys miss the point altogether. I know how to use workers from Swing and how to interact with Swing from other threads. In my case it really is the latter - it's not some GUI app with tasks triggered from UI. The whole thing is a sophisticated algorithm that runs in main thread and sometimes displays progress to user. – Konrad Garus Jun 03 '11 at 05:55
  • I think you're missing the point, @KonradGarus. If you use `SwingWorker`, you can entirely eliminate thread safety boilerplate. – Steve Feb 15 '13 at 01:54
1

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.

x4u
  • 13,877
  • 6
  • 48
  • 58
0

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);
        }
    });
 }
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Right. That example obviously was simplistic. What if you have 10 panels, and not just labels? Tables, progress bars, buttons, removing and adding components on the fly, and so on? Single helper for this simplistic case is fine, but I can't believe everyone is writing their own Swing-wrapping library for all the real life cases. – Konrad Garus Jun 02 '11 at 14:52
  • @Konrad, I agree. Swing doesn't follow any interfaces so you can't use a Proxy to do this for you. Aspect orientated programming might help, but I tend to avoid it myself. – Peter Lawrey Jun 02 '11 at 14:59
0

We used Spin in a project a few years back.

Goibniu
  • 2,212
  • 3
  • 34
  • 38
  • Similar to Spin, is [Foxtrot](http://foxtrot.sourceforge.net). Both seem abandonded. – Jim Jun 02 '11 at 16:17
0

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.

Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674