Возврат значений из Swing с помощью invokeAndWait

Я использую следующий подход для создания компонентов и возврата значений из Swing в/из вне EDT. Например, следующий метод может быть расширением JFrame для создания JPanel и добавления его к родительскому JFrame:

public JPanel threadSafeAddPanel() {

    final JPanel[] jPanel = new JPanel[1];

    try {
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                jPanel[0] = new JPanel();
                add(jPanel[0]);
            }
        });
    } catch (InterruptedException ex) {
    } catch (InvocationTargetException ex) {
    }

    return jPanel[0];
}

Локальный массив длиной 1 используется для передачи «результата» изнутри Runnable, который вызывается в EDT. Ну, это выглядит «немного» хакерским, и поэтому мои вопросы:

  1. Имеет ли это смысл? Кто-нибудь еще делает что-то подобное?
  2. Является ли массив 1 длины хорошим способом передачи результата?
  3. Есть ли более простой способ сделать это?

person Joonas Pulakka    schedule 08.03.2010    source источник


Ответы (4)


  • Проглатывать исключения, даже не регистрируя их: плохо!! — вы будете ненавидеть себя, когда столкнетесь с чем-то подобным после двухчасового поиска ошибок.
  • Нет, массив — не лучший способ; во-первых, он не предлагает для вызывающего кода простого способа дождаться, пока поток EDT выполнит Runnable, прежде чем получить результат.
  • Существует класс, специально разработанный для таких вещей: SwingWorker
person Michael Borgwardt    schedule 08.03.2010
comment
1. Вы правы :-) 2. Хорошо, но invokeAndWait делает это. 3. Возможно, мне следует использовать его, хотя для простых геттеров это кажется излишним. - person Joonas Pulakka; 08.03.2010
comment
1. SwingWorker — это не молоток, который можно использовать для устранения всех проблем с потоками в приложениях Swing. 2. SwingWorker предназначен для запуска вещей в фоновых потоках, но этот вопрос касается создания панели, которая в любом случае должна запускаться в EDT - полная противоположность тому, что SwingWorker помогает вам делать. - person Trejkaz; 21.03.2014

Хотя этот метод может иметь смысл в некоторых ситуациях, в большинстве случаев он бесполезен.

Причина в том, что создание большинства (если не всех) ваших компонентов всегда будет происходить из EDT в результате действий пользователя (элемент меню или нажатие кнопки), которые всегда выполняются из EDT.

В тех случаях, когда вам нужно выполнить большую работу перед созданием панели, и вы не хотите блокировать EDT, вам следует, как предложил кто-то другой, использовать SwingWorker или платформу Swing, которые предлагают поддержку длительных задач (обычно на основе В любом случае SwingWorker внутренне, но не обязательно).

Что касается вашего вопроса 2, к сожалению, у вас не так много способов сделать это:

  • Используйте массив из 1 элемента, как и вы, это самое простое, но и самое уродливое решение.
  • Создайте класс ItemHolder (см. ниже), который делает почти то же самое, требует немного больше работы и, на мой взгляд, чище.
  • Наконец, используйте возможности java.util.concurrent (Future и Callable); это было бы самым чистым, я думаю, но это также требует больше всего усилий

Вот упрощенный класс ItemHolder:

public class ItemHolder<T> {
    public void set(T item) {...}
    public T get() {...}
    private T item;
}
person jfpoilpret    schedule 09.03.2010
comment
+1 к предложению здесь об игнорировании проблемы с потоками, потому что есть вероятность, что этот метод все равно будет вызываться из EDT. Я бы просто утвердил isEventDispatchThread() и удалил весь шаблон — отложил бы проблему до тех пор, пока кто-то действительно не вызовет ее из другого потока, потому что YAGNI. - person Trejkaz; 21.03.2014

  1. а) Имеет смысл. б) не то, что я знаю.
  2. Так же хорош, как и любой.
  3. Создайте JPanel вне вызова invokeAndWait

//Эта строка добавлена, чтобы успокоить уценку

public JPanel threadSafeAddPanel() {
    final JPanel jPanel = new JPanel();
    try {
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                add(jPanel);
            }
        });
    } catch (InterruptedException ex) {
    } catch (InvocationTargetException ex) {
    }
    return jPanel;
}
person KitsuneYMG    schedule 08.03.2010
comment
Хотя 3., скорее всего, работает, строго говоря, это не разрешено. - person Joonas Pulakka; 08.03.2010
comment
Я согласен с Йоонасом, создание компонентов Swing вне EDT создает плохой прецедент, и его следует избегать. - person Nemi; 08.03.2010

Вы можете легко проверить, является ли текущий поток EDT, а затем выполнить правильно и проще в этом контексте. Что касается использования конечного массива для получения возвращаемого значения, это самый простой способ, когда вам нужно использовать анонимный внутренний класс, подобный этому.

public JPanel threadSafeAddPanel() throws InterruptedException, 
        InvocationTargetException {
    if (EventQueue.isDispatchThread()) {
        JPanel panel = new JPanel();
        add(panel);

        return panel; 
    } else {
        final JPanel[] jPanel = new JPanel[1];
        EventQueue.invokeAndWait(new Runnable() {
            public void run() {
                jPanel[0] = new JPanel();
                add(jPanel[0]);
            }
        });

        return jPanel[0];
    }
}
person Kevin Brock    schedule 09.03.2010