Шаблон безопасности резьбы Swing

Для простоты представьте себе приложение, которое загружает файл. Существует простой графический интерфейс с одной меткой, отображающей прогресс. Чтобы избежать нарушений EDT, я, как и каждый законопослушный гражданин, скачиваю файл в одном потоке (основном), а обновляю GUI в другом (EDT). Итак, вот соответствующий фрагмент псевдокода:

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

Мне нравится тот факт, что Java не поддерживает замыкания, и код кристально ясен для меня. Шутки в сторону, мне интересно... Я делаю это неправильно? Можно ли избавиться от всего этого уродливого шаблона с помощью SwingUtilities, анонимной реализации Runnable в каждом методе и т. д.?

Мой случай немного сложнее, чем этот, но я стараюсь не переусердствовать, внедряя прокси или что-то в этом роде.


person Konrad Garus    schedule 02.06.2011    source источник
comment
Я не думаю, что это настолько уродливо. По сравнению с закрытием у вас есть только дополнительные new Runnable() в основном.   -  person gpeche    schedule 02.06.2011
comment
По сравнению с этим закрытием? runInEdt({label.setText("Download started");}) Или даже: runInEdt(someBiggerAction), где someBiggerAction — имя метода. Я знаю, что я испорчен, и это никоим образом не возможно в Java, но я не могу представить, что после стольких лет все нарушают EDT или пишут эту 4-строчную обертку 100 раз.   -  person Konrad Garus    schedule 02.06.2011
comment
Как я уже сказал, единственная разница заключается в new Runnable. Вы можете получить runInEdt сейчас. На самом деле у вас уже есть, он называется SwingUtilities.invokeLater. Java немного многословен (по дизайну).   -  person gpeche    schedule 02.06.2011


Ответы (5)


Вы мало что можете сделать, чтобы избежать шаблонного кода, не вводя много избыточного кода в другом месте. Но вы можете сделать его немного лучше с помощью небольшого абстрактного вспомогательного класса и некоторого необычного форматирования.

public abstract static class SwingTask implements Runnable
{
    public void start()
    {
         SwingUtilities.invokeLater( this );
    }
}

startDownload() {
    new SwingTask() { public void run() {
        label.setText("Download started");
    } }.start();
}

Форматирование кода предназначено для того, чтобы подчеркнуть, что весь код в этой единственной строке представляет собой в основном неинтересный шаблонный код, который можно легко и безопасно скопировать в другие места, где он необходим. Мы уже давно используем этот стиль форматирования, и он оказался весьма полезным.

person x4u    schedule 02.06.2011

Для этих задач существует специальный класс: SwingWorker . Это позволяет вам запускать код в отдельном потоке и обновлять пользовательский интерфейс в конце работы.

person Vladimir Ivanov    schedule 02.06.2011
comment
Спасибо, но, пожалуйста, хотя бы прочитайте вопрос. Речь идет об исключении шаблона при обновлении компонентов Swing из потока, отличного от Swing, а не в том, как выполнять фоновые задачи из потока Swing. - person Konrad Garus; 02.06.2011
comment
Я согласен с @Vladimir. Во-первых, компоненты Swing не должны быть потокобезопасными, поэтому очень странно определять для них методы, вызывающие EDT. Скорее следует предположить, что методы компонентов Swing всегда вызываются из EDT, как и все методы компонентов Swing. Вы должны переосмыслить весь дизайн своей программы, и вы, вероятно, в конечном итоге будете использовать SwingWorker. - person toto2; 02.06.2011
comment
Это помимо сути. Я собираюсь в конечном итоге иметь этот шаблон где-нибудь. (Должен ли я? Вот что я спрашиваю здесь) - person Konrad Garus; 03.06.2011
comment
Выполнение этого в компоненте лучше для тестируемости — вы можете определить интерфейс для ProgressMonitor и просто вызывать start(), showProgress() и stop(), не заставляя пользователя (DownloadManager) помнить о EDT. Возможно, я бы сказал, что это лучший API (и я думаю, что это так в моем приложении). - person Konrad Garus; 03.06.2011
comment
Наконец, вы, ребята, совсем упускаете суть. Я знаю, как использовать воркеры из Swing и как взаимодействовать со Swing из других потоков. В моем случае это действительно последнее - это не какое-то приложение с графическим интерфейсом, задачи которого запускаются из пользовательского интерфейса. Все это представляет собой сложный алгоритм, который работает в основном потоке и иногда отображает ход выполнения для пользователя. - person Konrad Garus; 03.06.2011
comment
Я думаю, ты упускаешь суть, @KonradGarus. Если вы используете SwingWorker, вы можете полностью исключить шаблон безопасности потоков. - person Distortum; 15.02.2013

Я бы использовал вспомогательный метод, например.

 public void setLabelText(final JLabel label, final String text) {
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            label.setText(text);
        }
    });
 }
person Peter Lawrey    schedule 02.06.2011
comment
Правильно. Этот пример явно был упрощенным. А если у вас 10 панелей, а не только этикетки? Таблицы, индикаторы выполнения, кнопки, удаление и добавление компонентов на лету и так далее? Один помощник для этого упрощенного случая — это хорошо, но я не могу поверить, что все пишут свою собственную библиотеку Swing-обертки для всех реальных случаев. - person Konrad Garus; 02.06.2011
comment
@Конрад, согласен. Swing не поддерживает никаких интерфейсов, поэтому вы не можете использовать прокси, чтобы сделать это за вас. Аспектно-ориентированное программирование может помочь, но я сам его избегаю. - person Peter Lawrey; 02.06.2011

Мы использовали Spin в проекте несколько лет назад.

person Goibniu    schedule 02.06.2011
comment
Подобно Spin, это Foxtrot. Оба кажутся заброшенными. - person Jim; 02.06.2011

Просто сырая идея:

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

}

Не пробовал, но думаю, что при вызове setText() из EDT будет использоваться super; но когда задействован другой поток, переопределенный (ThreadSafeJLabel.setText()) будет вызван позже, на этот раз внутри EDT.

person Tomasz Nurkiewicz    schedule 02.06.2011