1. Что такое EDT (Event Dispatch Thread)
В графических приложениях на Java — будь то Swing или JavaFX — все действия пользователя (клики, клавиши), а также перерисовка окон обрабатываются в специальном потоке под названием EDT (Event Dispatch Thread, поток обработки событий).
Зачем он нужен? Компоненты UI в Java не потокобезопасны. Чтобы избежать гонок и артефактов, все изменения интерфейса выполняются строго в одном месте — в EDT. Это как касса с одним кассиром: одним и тем же чеком не могут одновременно заниматься несколько людей.
В Swing EDT запускает обработчики событий и перерисовку компонентов (например, actionPerformed). В JavaFX аналог — JavaFX Application Thread, на котором выполняются обновления UI и обработчики вроде setOnAction.
2. Проблема «долгих операций» в UI
Что происходит, если в EDT запустить долгую операцию?
Когда пользователь нажимает кнопку, обработчик (например, actionPerformed или setOnAction) выполняется на EDT. Если внутри вы запускаете тяжёлую задачу (чтение большого файла, сетевой запрос, сложные вычисления), весь UI «зависает»:
- Окно перестаёт реагировать на клики и клавиши.
- Перестаёт работать перерисовка — при перемещении окно «залипает».
- Пользователь решает, что программа «сломалась».
Пример неправильного кода (Swing):
button.addActionListener(e -> {
// Долгая операция прямо в EDT!
longOperation(); // Например, чтение большого файла
label.setText("Готово!");
});
Результат: пока выполняется longOperation(), окно не реагирует на пользователя.
Почему так? EDT обрабатывает задачи по очереди и может выполнять только одну за раз. Пока он занят вашей долгой операцией, он не может обрабатывать ни клики, ни перерисовку.
3. Решение: долгие операции — только в фоновых потоках
Принцип:
- Все долгие операции — только в фоновых потоках.
- Все изменения UI — только в EDT/JavaFX Application Thread.
Запускаем долгую операцию в отдельном потоке
Пример (Swing):
button.addActionListener(e -> {
new Thread(() -> {
longOperation(); // Выполняется в фоновом потоке
// Теперь нужно обновить UI — но только из EDT!
SwingUtilities.invokeLater(() -> label.setText("Готово!"));
}).start();
});
Пример (JavaFX):
button.setOnAction(e -> {
new Thread(() -> {
longOperation();
// Обновляем UI через Platform.runLater
Platform.runLater(() -> label.setText("Готово!"));
}).start();
});
Как обновлять UI из фонового потока?
- Swing: используйте SwingUtilities.invokeLater(Runnable) — задача попадёт в очередь EDT.
- JavaFX: используйте Platform.runLater(Runnable) — задача выполнится в JavaFX Application Thread.
Почему нельзя просто вызвать label.setText(...) из фонового потока? Потому что это нарушение потокобезопасности UI: компоненты должны меняться только из потока интерфейса.
Специальные классы для фоновых задач
В реальных приложениях часто нужно показывать прогресс, позволять отмену и обрабатывать ошибки. Для этого существуют:
- SwingWorker<T, V> — для Swing;
- Task<V>, Service<V> — для JavaFX.
Пример (JavaFX Task):
Task<Void> task = new Task<>() {
@Override
protected Void call() throws Exception {
longOperation();
// Можно обновлять прогресс: updateProgress(...)
return null;
}
};
task.setOnSucceeded(e -> label.setText("Готово!"));
task.setOnFailed(e -> label.setText("Ошибка!"));
new Thread(task).start();
Преимущества: прогресс, отмена, события успеха/ошибки. Изменение UI — через безопасные методы (updateMessage, updateProgress) или обработчики (setOnSucceeded и др.).
4. Правильные и неправильные паттерны
Неправильно: долгие операции в обработчике событий
button.setOnAction(e -> longOperation()); // UI зависнет!
Правильно: долгие операции в отдельном потоке
button.setOnAction(e -> new Thread(() -> longOperation()).start());
Ещё лучше: использовать Task/Worker
JavaFX:
button.setOnAction(e -> {
Task<Void> task = new Task<>() {
@Override
protected Void call() throws Exception {
longOperation();
return null;
}
};
task.setOnSucceeded(ev -> label.setText("Готово!"));
new Thread(task).start();
});
Swing:
button.addActionListener(e -> {
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
longOperation();
return null;
}
@Override
protected void done() {
label.setText("Готово!");
}
};
worker.execute();
});
5. Практика: пример с загрузкой файла
JavaFX:
button.setOnAction(e -> {
Task<String> task = new Task<>() {
@Override
protected String call() throws Exception {
// Имитация долгой загрузки
Thread.sleep(2000);
return "Файл загружен!";
}
};
task.setOnSucceeded(ev -> label.setText(task.getValue()));
new Thread(task).start();
});
Swing:
button.addActionListener(e -> {
SwingWorker<String, Void> worker = new SwingWorker<>() {
@Override
protected String doInBackground() throws Exception {
Thread.sleep(2000);
return "Файл загружен!";
}
@Override
protected void done() {
try {
label.setText(get());
} catch (Exception ex) {
label.setText("Ошибка!");
}
}
};
worker.execute();
});
6. Типичные ошибки при работе с EDT и долгими операциями
Ошибка №1: Долгая операция в EDT. Всё приложение «зависает», окно не реагирует, пользователь думает, что программа сломалась.
Ошибка №2: Попытка обновить UI из фонового потока. Нарушение потокобезопасности UI может привести к багам, артефактам и падениям. Используйте SwingUtilities.invokeLater или Platform.runLater.
Ошибка №3: Отсутствие обработки ошибок в фоновой задаче. Исключения «теряются», пользователь не знает, что пошло не так. В Swing — переопределяйте done() и читайте get(); в JavaFX — подписывайтесь на setOnFailed.
Ошибка №4: Нет возможности отменить долгую операцию. Пользователь не может прервать загрузку/вычисление. Используйте поддержку отмены (SwingWorker.cancel, Task.cancel) и проверяйте флаги отмены внутри задачи.
Ошибка №5: Нет индикации прогресса. Пользователь думает, что программа «зависла». В Swing — используйте публикацию результатов и прогрессбар вместе с SwingWorker; в JavaFX — updateProgress и визуальные индикаторы.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ