Как вы используете поток отправки событий?

#java #swing #thread-safety #event-dispatch-thread

#java #swing #безопасность потоков #event-dispatch-thread

Вопрос:

Я узнал о том, что swing не является потокобезопасным. Углубившись, я обнаружил, что каждое изменение компонента swing должно выполняться в потоке отправки событий, чтобы предотвратить различные проблемы, связанные с многопоточностью. Однако информация, казалось, полностью остановилась на этом. Кажется, нет хорошего руководства, объясняющего, как это сделать в любом месте, доступном в Интернете.

Исправляя информацию из кода, опубликованного в связи с другими проблемами, казалось, что мне придется помещать неопрятный блок кода вокруг каждой отдельной модификации swing в моей программе (например, этот пример из моего собственного кода):

 try {
        SwingUtilities.invokeAndWait(new Runnable() {

            public void run() {
                setTitle("Frame title");
                setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                setVisible(true);

                setSize(800, 480);
                setLocationRelativeTo(null);
                setIconImage(Toolkit.getDefaultToolkit().createImage(ClassLoader.getSystemResource("Frame icon.png")));
            }
        });
    } catch (Exception e) {
        e.printStackTrace();
    }
 

В принципе, это правильно? Должен ли я помещать этот код (или эквивалент с invokeLater) вокруг каждой модификации компонента Swing в моем коде?

Кроме того, почему Swing не делает это автоматически?

Ответ №1:

Хитрость в том, что когда swing вызывает вас, он ВСЕГДА будет в EDT, поэтому вам не нужно беспокоиться об этом.

Однако, если вы находитесь в таймере или действии, вызванном каким-либо другим внешним событием, вашим основным потоком или любым другим потоком, который вы создали, тогда да, вы должны использовать invokeLater или invokeAndWait .

Другими словами, да, swing делает «это» автоматически. Необходимость использования invokeXx настолько редка, что если бы swing выполнял это внутренне, это отняло бы слишком много времени.

Многие java-программисты никогда не понимают этого, и это может вызвать некоторые довольно неприятные труднодоступные проблемы с рисованием вашего GUI. Я бы хотел, чтобы swing выдавал исключение, когда вы вызывали его, не используя EDT — у Java была бы лучшая репутация, когда дело доходило до профессиональных графических интерфейсов, если бы это было так, потому что там было бы меньше дерьма.

Комментарии:

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

2. У Microsoft в эпоху Windows 3.0 / 3.1 были «отладочные двоичные файлы», которые выполняли бы такую проверку, чтобы вам не приходилось перезагружаться каждый раз, когда вы отправляли неверные аргументы. Меня всегда беспокоило, что в Java нет того же самого — тем более, что он достаточно умен, чтобы иметь возможность скомпилировать его с помощью переключателя командной строки.

3. Раньше я всегда помещал его «в» приложение Java, заменяя менеджер (ы) перерисовки на CheckThreadViolationRepaintManager . Я помню, что это работало довольно хорошо 🙂

4. Теперь, когда я думаю об этом в наши дни, это, вероятно, легко решить с помощью аспектов, но, с другой стороны, я беспокоюсь не столько о СЕБЕ, сколько о том, что все остальные заставляют людей думать, что хорошего java gui не существует. Еще одной хорошей проверкой было бы убедиться, что поток EDT никогда не тратит в вашем коде более 0,01 секунды или около того — дольше, и он должен выдать исключение, сообщающее вам поместить его в рабочий поток.

Ответ №2:

обратите внимание, что любой код, выполняемый из обработчиков событий, уже выполняется в EDT (событие в аббревиатуре)

это означает, что для общего использования (пока вы не связываетесь с swingworkers, threadpools и т. Д.) Вы всегда находитесь внутри EDT

и вы всегда можете запросить, находитесь ли вы в EDT с SwingUtilities.isEventDispatchThread()

также обратите внимание, что в вашем коде вызов invokeAndWait завершится ошибкой и выдаст ошибку, когда вы уже находитесь в EDT

Ответ №3:

По сути, вы не рисуете и не обновляете графический интерфейс извне EDT. Вы используете SwingUtilitis.invokeLater() из другого потока, чтобы гарантировать, что код рисования или обновления графического интерфейса выполняется в EDT.

Комментарии:

1. Просто код чертежа? Могу ли я избавиться от неопрятности вокруг таких вещей, как setSize() и setVisible() ?

2. при рисовании кода это подразумевает любые обновления компонентов графического интерфейса, так что да, setSize() и setVisible() являются частью кода рисования.

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

Ответ №4:

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