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.
GO TO FULL VERSION