CodeGym /Kursy /JAVA 25 SELF /Wątek EDT i długie operacje w UI

Wątek EDT i długie operacje w UI

JAVA 25 SELF
Poziom 50 , Lekcja 4
Dostępny

1. Czym jest EDT (Event Dispatch Thread)

W aplikacjach graficznych w Javie — czy to Swing, czy JavaFX — wszystkie działania użytkownika (kliknięcia, naciśnięcia klawiszy), a także odświeżanie okien są obsługiwane w specjalnym wątku o nazwie EDT (Event Dispatch Thread, wątek obsługi zdarzeń).

Po co jest potrzebny? Komponenty UI w Javie nie są bezpieczne wątkowo. Aby uniknąć wyścigów i artefaktów, wszystkie zmiany interfejsu wykonuje się wyłącznie w jednym miejscu — w EDT. To jak kasa z jednym kasjerem: tym samym paragonem nie może jednocześnie zajmować się kilka osób.

W Swing EDT uruchamia obsługę zdarzeń i odświeżanie komponentów (na przykład actionPerformed). W JavaFX odpowiednikiem jest JavaFX Application Thread, na którym wykonywane są aktualizacje UI i handlery w rodzaju setOnAction.

2. Problem „długich operacji” w UI

Co się stanie, jeśli w EDT uruchomimy długą operację?

Gdy użytkownik naciska przycisk, handler (na przykład actionPerformed lub setOnAction) wykonuje się na EDT. Jeśli w środku uruchomisz ciężkie zadanie (odczyt dużego pliku, żądanie sieciowe, złożone obliczenia), cały UI „zawiesza się”:

  • Okno przestaje reagować na kliknięcia i klawisze.
  • Przestaje działać odświeżanie — przy przenoszeniu okno „zamraża się”.
  • Użytkownik uznaje, że program się „zepsuł”.

Przykład błędnego kodu (Swing):

button.addActionListener(e -> {
    // Długa operacja bezpośrednio w EDT!
    longOperation(); // Na przykład odczyt dużego pliku
    label.setText("Gotowe!");
});

Wynik: dopóki longOperation() się wykonuje, okno nie reaguje na użytkownika.

Dlaczego tak się dzieje? EDT przetwarza zadania kolejno i może wykonywać tylko jedno naraz. Dopóki jest zajęty twoją długą operacją, nie może obsługiwać ani kliknięć, ani odświeżania.

3. Rozwiązanie: długie operacje — tylko w wątkach w tle

Zasada:

  • Wszystkie długie operacje — tylko w wątkach w tle.
  • Wszystkie zmiany UI — tylko w EDT/JavaFX Application Thread.

Uruchamiamy długą operację w oddzielnym wątku

Przykład (Swing):

button.addActionListener(e -> {
    new Thread(() -> {
        longOperation(); // Wykonywana w wątku w tle
        // Teraz trzeba zaktualizować UI — ale tylko z EDT!
        SwingUtilities.invokeLater(() -> label.setText("Gotowe!"));
    }).start();
});

Przykład (JavaFX):

button.setOnAction(e -> {
    new Thread(() -> {
        longOperation();
        // Aktualizujemy UI przez Platform.runLater
        Platform.runLater(() -> label.setText("Gotowe!"));
    }).start();
});

Jak aktualizować UI z wątku w tle?

  • Swing: użyj SwingUtilities.invokeLater(Runnable) — zadanie trafi do kolejki EDT.
  • JavaFX: użyj Platform.runLater(Runnable) — zadanie wykona się w JavaFX Application Thread.

Dlaczego nie można po prostu wywołać label.setText(...) z wątku w tle? Bo to narusza bezpieczeństwo wątkowe UI: komponenty należy modyfikować wyłącznie z wątku interfejsu.

Specjalne klasy do zadań w tle

W prawdziwych aplikacjach często trzeba pokazywać postęp, umożliwiać anulowanie i obsługiwać błędy. Służą do tego:

  • SwingWorker<T, V> — dla Swing;
  • Task<V>, Service<V> — dla JavaFX.

Przykład (JavaFX Task):

Task<Void> task = new Task<>() {
    @Override
    protected Void call() throws Exception {
        longOperation();
        // Można aktualizować postęp: updateProgress(...)
        return null;
    }
};

task.setOnSucceeded(e -> label.setText("Gotowe!"));
task.setOnFailed(e -> label.setText("Błąd!"));

new Thread(task).start();

Zalety: postęp, anulowanie, zdarzenia sukcesu/błędu. Zmiany UI — przez bezpieczne metody (updateMessage, updateProgress) lub handlery (setOnSucceeded itd.).

4. Poprawne i błędne wzorce

Niepoprawnie: długie operacje w obsłudze zdarzenia

button.setOnAction(e -> longOperation()); // UI się zawiesi!

Poprawnie: długie operacje w oddzielnym wątku

button.setOnAction(e -> new Thread(() -> longOperation()).start());

Jeszcze lepiej: użyć 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("Gotowe!"));
    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("Gotowe!");
        }
    };
    worker.execute();
});

5. Praktyka: przykład z wczytywaniem pliku

JavaFX:

button.setOnAction(e -> {
    Task<String> task = new Task<>() {
        @Override
        protected String call() throws Exception {
            // Symulacja długiego ładowania
            Thread.sleep(2000);
            return "Plik wczytany!";
        }
    };
    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 "Plik wczytany!";
        }
        @Override
        protected void done() {
            try {
                label.setText(get());
            } catch (Exception ex) {
                label.setText("Błąd!");
            }
        }
    };
    worker.execute();
});

6. Typowe błędy przy pracy z EDT i długimi operacjami

Błąd nr 1: Długa operacja w EDT. Cała aplikacja „zawiesza się”, okno nie reaguje, użytkownik myśli, że program się zepsuł.

Błąd nr 2: Próba aktualizacji UI z wątku w tle. Naruszenie bezpieczeństwa wątkowego UI może prowadzić do błędów, artefaktów i awarii. Używaj SwingUtilities.invokeLater lub Platform.runLater.

Błąd nr 3: Brak obsługi błędów w zadaniu działającym w tle. Wyjątki „gubią się”, użytkownik nie wie, co poszło nie tak. W Swing — nadpisuj done() i odczytuj get(); w JavaFX — podpinaj się pod setOnFailed.

Błąd nr 4: Brak możliwości anulowania długiej operacji. Użytkownik nie może przerwać ładowania/obliczeń. Korzystaj z obsługi anulowania (SwingWorker.cancel, Task.cancel) i sprawdzaj flagi anulowania wewnątrz zadania.

Błąd nr 5: Brak wskazania postępu. Użytkownik myśli, że program się „zawiesił”. W Swing — używaj publikowania wyników i paska postępu wraz z SwingWorker; w JavaFX — updateProgress i wizualne wskaźniki.

1
Ankieta/quiz
Zdarzenia i obsługa zdarzeń, poziom 50, lekcja 4
Niedostępny
Zdarzenia i obsługa zdarzeń
Zdarzenia i obsługa zdarzeń
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION