JavaRush /Курси /JAVA 25 SELF /Потік EDT і тривалі операції в UI

Потік EDT і тривалі операції в UI

JAVA 25 SELF
Рівень 50 , Лекція 4
Відкрита

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 і візуальні індикатори.

1
Опитування
Події та обробка подій, рівень 50, лекція 4
Недоступний
Події та обробка подій
Події та обробка подій
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ