#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 только тогда, когда вы находитесь в другом потоке.