Jak wykonywać długotrwałe zadania w Swing’u

Odkąd zacząłem pisać w Javie bardziej złożone aplikacje, które musiały na przykład komunikować się z jakimiś webowymi usługami lub wykonywać dłuższe obliczenia, miewałem problemy z zamarzającym GUI. Próbowałem problem ten rozwiązać na wiele sposobów, jednak zawsze znalazło się coś, co nie działało tak jak powinno – jak chociażby labele lub pola tekstowe, które powinny wyświetlać informacje o postępie zadania, a przez cały czas aż do zakończenia zadania pozostawały niezmienione.

Skonsultowałem się z bratem Google i zapoznałem z kilkoma odnalezionymi przy jego pomocy linkami, dzięki którym udało mi się opracować wizję działania Swinga i wątków w Javie.

I tak oto wszystko rozbija się o EDT, czyli Event Dispatch Thread. Jest to wątek, w którym są odrysowywane okna wraz z zawartością oraz obsługiwane wywołane z GUI akcje i zdarzenia. Ma to swoje plusy, gdyż na przykład zmiana zawartości labela w reakcji na wciśnięcie przycisku odniesie natychmiastowy i widoczny skutek. Jednak, jeśli w akcji tego samego przycisku odpalimy algorytm obliczający liczbę PI, to okaże się, że okienka nie odpowiadają, gdyż wątek EDT odpowiedzialny za ich odrysowywanie i obsługę, jest zajęty obliczaniem PI.

Logicznym w takiej sytuacji wydawałoby się, aby w akcji przycisku odpalić nasz algorytm w osobnym wątku, który będzie działał równolegle z EDT. Gdy tak zrobimy okaże się, że faktycznie nasze okienko nie będzie już blokowane. Pojawi się jednak inny problem, gdy z wątku obliczającego algorytm będziemy chcieli na przykład zmienić wartość pola tekstowego zawierającego obliczoną dotychczas wartość liczby PI. Problem ten związany jest z tym, że metody zmieniające coś w GUI, takie jak na setText(…) powinny być wywoływane z poziomu EDT, gdyż wywołanie ich poza kontrolą tego wątku nie daje nam gwarancji, że odniosą natychmiastowy skutek. Rozwiązaniem tego problemu jest klasa SwingUtilities i jedna z jej metod, tj. invokeLater.

Poniżej złączam przerobiony przeze mnie kod przykładowej aplikacji, pokazujący jak powinno się wykonywać długotrwałe zadania w Swingu oraz jakich pomyłek należy się wystrzegać. Jako, że od Javy 1.6 mamy dostęp do klasy SwingWorker, która przeznaczona jest do wykonywania takich właśnie zadań, załączam również przykład wykorzystania tej klasy.

Tak wygląda okno testowej aplikacji:

invokeExample

Przydatne linki:
Concurrency in Swing
SwingWorker
How to handle long-running tasks in a Swing application

2 komentarze

  1. OstroS napisał(a):

    Bardzo przydatny artykuł, sam zastanawiałem się przy okazji poprzedniego projektu (zawierający upload/download bardzo dużej ilości plików) jak to sprytnie rozwiązać. Napisałem trochę na około i jakoś działało, szkoda że tego rozwiązania nie znałem.

    Dzięki 🙂

  2. daniio napisał(a):

    Wielkie dzięki. Bardzo fajnie wytłumaczone. Przykład w 10. Artykuł stary ale napiszę co mnie zastanowiło. Mianowicie kiedy odpala się wątek EWT. Po wygooglowaniu okazuje się, że bardzo różnie startuje. Często zależy to od tego z jakiego pakietu JDK korzystamy i dla jakiej platformy.

    Pozdro

Leave a Reply