Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Poczatki

14 views
Skip to first unread message

ag :)

unread,
Jan 5, 2010, 2:45:59 PM1/5/10
to
Witam

int i;
int max=9;

for (i=0;i<=max;i++){
jTextArea1.append(Integer.toString(i));
Thread.sleep(200);
}

czyli banal - chce po kolei wypisywac cyfry 0...9 i sobie postac
pomiedzy wypisywaniem kazdej. Oczywiscie w tej postaci najpierw
"czeka", potem hurtem wypisuje cyfry i od razu mam wynik.

Watki? Jak to ladnie ubrac w kod zeby dzialalo?

z gory dzieki.

Brzezi

unread,
Jan 5, 2010, 4:18:26 PM1/5/10
to

Tomek Nurkiewicz

unread,
Jan 5, 2010, 5:12:43 PM1/5/10
to
>> czyli banal - chce po kolei wypisywac cyfry 0...9 i sobie postac
>> pomiedzy wypisywaniem kazdej. Oczywiscie w tej postaci najpierw
>> "czeka", potem hurtem wypisuje cyfry i od razu mam wynik.
>>
>> Watki? Jak to ladnie ubrac w kod zeby dzialalo?
>
> http://java.sun.com/docs/books/tutorial/uiswing/concurrency/index.html
> http://java.sun.com/products/jfc/tsc/articles/threads/threads1.html
>
> Pozdrawiam
> Brzezi

I troch� na skr�ty:

http://java.sun.com/docs/books/tutorial/uiswing/misc/timer.html

pozdrowienia

--
Tomek Nurkiewicz
http://nurkiewicz.blogspot.com

Witold Szczerba

unread,
Jan 5, 2010, 6:43:49 PM1/5/10
to

Żeby zrozumieć co jest tutaj źle musisz wiedzieć, że Swing działa w
jednym, specjalnym wątku zwanym w skrócie EDT (Event Dispatch Thread).
Twój kod też działa w tym wątku, a cały proces wygląda tak:

1) program dochodzi do momentu, w którym aplikacja czeka na jakąś
akcję, np. wciśnięcie przycisku

2) wciskasz przycisk

3) Swing przyciemnia przycisk, żeby pokazać, że jest właśnie wciśnięty
(ale po kliknięciu przycisk nie wraca z powrotem tak, jakby się można
spodziewać, tylko pozostaje w takim stanie, a cała aplikacja wygląda
jakby się zawiesiła)

4) uruchamiany jest twój kod, (na skutek akcji wywołanej przez
komponent Swingowy), tak więc dzieje się to w wątku EDT. Możesz to
sprawdzić logując na konsolę wynik działania:
EventQueue.isDispatchThread();

5) kod w pętli zmienia model pola tekstowego, model reagując na zmianę
przygotowywuje za każdym razem nowy obiekt zdarzenia (event)
opisującego zmianę tego pola tekstowego i wysyła (dispatch) ten obiekt
do kolejki (queue). Taki obiekt czeka sobie w kolejce (event queue),
ale nie jest zbierany przez Swinga, bo jest on aktualnie zajęty twoim
kodem, który co chwilę każe mu iść spać na 200ms.

6) twój kod dobiega końca, w kolejce Swinga nazbierało się już całkiem
sporo zdarzeń do przetworzenia, teraz Swing jest już wolny więc
przetwarza po kolei te zdarzenia z szybkością Strusia Pędziwiatra
(nikt mu przecież nie każe czekać, więc zapierdziela ile fabryka
dała). Być może jednym z pierwszych zdarzeń jakie dostanie do
wykonania będzie to czekające najdłużej - czyli od-ciśnięcie tego
przycisku, potem natrafi na poszczególne zmiany w polu tekstowym i co
tam jeszcze się nie nazbiera.

Teraz masz już odpowiedź dlaczego działa tak jak działa.
Teraz są dwie opcje. Najpierw pierwsza, która zniweluje problem, ale
tylko połowicznie. Zmień kod tak:

jTextArea1.append(Integer.toString(i));
//teraz uwaga, czary-mary:
jTextArea1.repaint();
Thread.sleep(200);

Powinno zadziałać stopniowe pokazywanie się liczb, ale nadal aplikacja
będzie jakby zawieszona aż pętla nie skończy. Potraktuj to tylko jaki
taki mały trick, bo prawdziwe KNOW-HOW masz poniżej.

Zapewne przyjdzie na myśl zrobienie czegoś takiego, żeby przycisk nie
uruchamiał twojego kodu w wątku (EDT) tylko żeby uruchomił twój kod w
innym wątku i będzie po sprawie... i... będziesz miał rację. Osobny
wątek nie będzie blokował Swingowego EDT, więc co 200ms dostaniesz
kolejne liczby na ekranie. W trakcie wypisywania tych liczb przycisk
będzie reagował, aplikacja nie będzie wyglądała jakby się zawiesiła i
w ogóle będzie po problemie... prawie... Twój przykład jest o tyle
prosty, że cała twoja komunikacja ze Swingiem to jest wywołanie metody
#setText(String) na obiekcie typu JTextArea. Tak się akurat składa, że
ta metoda jako jedna z _nielicznych_ może być uruchamiana z dowolnego
wątku aplikacji, ale _zdecydowana_ większość innych metod już takiej
swobody nie daje i trzeba być w EDT, żeby z nich korzystać. Skąd to
wiadomo? Cytat z JavaDoc opisujący tę metodę:

-------
public void setText(String t)

This method is thread safe, although most Swing methods are not.
Please see How to Use Threads for more information. Note that text is
not a bound property, so no PropertyChangeEvent is fired when it
changes. To listen for changes to the text, use DocumentListener.
[...]
-------

Dlatego takie rozwiązanie, żeby twój kod takim jaki jest wstawić w
Runnable i wysłać do nowego wątku, nie jest zalecane. Zadziała i
będzie poprawne, ale jak na polu minowym. Dodasz jakiś fragment, który
odpala metodę, która MUSI być wywołana z poziomu EDT i aplikacja może
zacząć wariować (np. dziwne, trudne do reprodukcji błędy mogą się
pojawiać całkiem losowo, tragedia jeśli chodzi o szukanie takich
przyczyn). Dlatego tego typu rzeczy robi się tak, że jeśli twój kod
robi coś w innym wątku niż EDT i chce wywołać jakąś metodę Swinga, to
wywołanie opakować należy w Runnable i wysyła do kolejki metodą:

EventQueue.invokeLater(Runnable r);

Swing dostanie twój komunikat zawierający kawałek kodu i uruchomi go w
wątku EDT, a twój wątek, na nic nie czekając, będzie robił dalej
swoje. Możesz to sprawdzić sam bardzo prosto, polecam w celach
ćwiczeniowych (porównasz sobie to z przykładem, który opiszę dalej, i
będziesz miał jak na dłoni po co powstał SwingWorker). Więc
klasycznie: cały swój kod opakuj w Runnable i uruchom w nowym wątku,
dodatkowo samo tylko wywołanie jTextArea1.setText(xxx) dodatkowo
opakuj w Runnable i wysyłaj w tej pętli do EventQueue metodą
invokeLater.


Twórcy Swinga przygotowali jednak kilka klas pomocniczych, które
upraszczają najczęściej występujące przypadki. Taki przypadek jak
opisałeś doskonale obsługuje klasa SwingWorker<T,V>,gdzie

T to jest wynik ostateczny wynik działania całego zadania (w twoim
przypadku nie ma nic takiego, więc wstawisz tam po prostu Void),

V to jest typ, który chcesz w trakcie działania twojego kodu
cyklicznie przekazywać do metody obsługującej powiadamianie, w twoim
przypadku to będzie Integer.

OK, zabierzmy się za przerobienie twojego kodu tak, żeby wykorzystał
SwingWorkera.

SwingWorker daje nam do dyspozycji m.in. takie metody:
1) public T doInBackground()
2) protected void process(List<V> chunks)
3) protected void done()
4) protected void publish(V... chunks)

Metoda 1 musi być przez nas zaimplementowana - ona rozpoczyna proces,
tam wstawimy logikę, która nie zablokuje nam EDT. Metoda 2 będzie nam
aktualizowała pole tekstowe i robiła dowolne inne rzeczy ze Swingiem,
ponieważ ona będzie uruchamiana w EDT, metoda 3 jest nam wg twojego
przykładu zupełnie do niczego niepotrzebna więc ją ignorujemy (ale tak
na przyszłość - też wywoła się ona w EDT), natomiast metoda 4 to jest
pomost pomiędzy naszym kodem z punktu 1 (uruchomionym w osobnym wątku)
a kodem aktualizującym cyklicznie pole tekstowe z punktu 2. Metoda 4
to jest taki pośrednik. Uruchamiamy ją z przekazując komunikat (naszą
liczbę) a ona odkłada sobie to "na bok" i jak Swing zgłosi gotowość,
to wszystko co czeka w kolejce jest przekazywane do listy i
uruchamiana jest metoda 2 już w EDT. Brzmi skomplikowanie, ale to
proste.

Tak więc metoda 1 używa metody 4 i będzie wyglądać tak:

@Override
public Void doInBackground() {
int max=9;
for (int i=0; i<=max; i++) {
publish(i); //wysyłamy liczbę i
Thread.sleep(200);
}
return null;
}

Prawda, że proste? Teraz metoda 2:

@Override
protected void process(List<Integer> chunks) {
//pobieramy naszą liczbę, jesteśmy teraz w EDT:
for (Integer chunk : chunks) {
jTextArea1.append(""+chunk);
}
}

Ot, cała filozofia. Teraz tylko pytanie - jak to odpalić? SwingWorker
implementuje m.in. Runnable, co daje wiele możliwości, np.
a) zrobić nowy wątek i go uruchomić:
new Thread(naszSwingWorker).start()

b) można użyć coś z java.util.Concurrent, np. Executor:
executor.execute(naszSwingWorker);
np. ja tak używam, bo to daje ekstra możliwości, które wykraczają poza
temat tego wątku

c) można wreszcie samemu go odpalić na naszym wątki przez:
naszSwingWorker.run()
ale wtedy uzyskamy dokładnie to samo co na początku: #doInBackground()
wywoła nam się w EDT i będzie kiepsko. Jeśli natomiast nie jesteśmy w
EDT tylko w jakimś swoim wątku, wtedy takie odpalenie będzie dobre,
ale zablokuje nam nasz wątek.

SwingWorker jest jednak tak wspaniałomyślny, że dla uproszczenia jest
w nim też metoda, która sama sobie zorganizuje całość automatycznie:
naszSwingWorker.execute();
uruchomi go w swojej specjalnie przygotowanej puli wątków, pełny
automat, nic tylko wywołać to #execute z dowolnego wątku (czy to
będzie EDT czy jakiś inny - nie ma znaczenia) i po sprawie... :)

Filip Sielimowicz

unread,
Jan 6, 2010, 8:37:55 AM1/6/10
to
:) Dobry wyk�adzik.
Kiedy� wszystko trzaska�em na r�cznych wrzutach do EventQueue a na
przerobienie kodu na SwingWorkera mia�em ochote, ale za ma�o determinacji,
by w to wchodziďż˝. Gdyby ten post trafiďż˝ mi siďż˝ ze trzy lata temu ;)

Andrzej S.

unread,
Jan 7, 2010, 6:10:48 AM1/7/10
to
Witold Szczerba pisze:
>
> �eby zrozumie� co jest tutaj �le musisz wiedzie�, �e Swing dzia�a w
> jednym, specjalnym w�tku zwanym w skr�cie EDT (Event Dispatch Thread).
> Tw�j kod te� dzia�a w tym w�tku, a ca�y proces wygl�da tak:
...

Panie Witoldzie, znakomicie Pan to napisal!

Jesli mozna jeszcze prosic o rozjasnienie: mamy tez InvokeAndWait()
Rozumiem, ze te procesy musza byc wykonane _teraz_, gdy InvokeLater()
zrobi to w wolnej chwili?

I dlaczego EventQueue.Invoke... a nie SwingUtilities.Invoke... ?

pozdr serd
--
A S

Witold Szczerba

unread,
Jan 7, 2010, 7:26:57 AM1/7/10
to
On 7 Sty, 12:10, "Andrzej S." <A_S@nie_nie.pl> wrote:
> Jesli mozna jeszcze prosic o rozjasnienie: mamy tez InvokeAndWait()
> Rozumiem, ze te procesy musza byc wykonane _teraz_, gdy InvokeLater()
> zrobi to w wolnej chwili?

Na początek bardzo proszę o korztystanie z JavaDoc. Obowiązkowo pobrać
na dysk, rozpakować i dodać do zakładki w swojej ulubionej
przeglądarce WWW i używać, używać, używać.

Wracając do pytania: różnica między InvokeAndWait a InvokeLater jest
taka, że ta pierwsza uruchamiana jest synchronicznie (i nie wolno
uruchomić jej z EDT), a ta druga asynchronicznie. Tak więc ta pierwsza
wrzuci kod do kolejki i zablokuje aktualny wątek (dlatego to nie może
być EDT) aż do czasu, aż przekazany jej Runnable nie zakończy
działania. Przy okazji, ponieważ działa synchronicznie, może ona
przechwycić wyjątki wyrzucone przez zawarty wewnątrz Runnable kod.
InvokeLater działa inaczej: wysyła Runnable do kolejki i co się dalej
dzieje jej już nie interesuje, ona zaraz po umieszczeniu kodu w
kolejce kontynuuje swoje działanie (to tak, jakbyś wysłał email - twój
komputer nie czeka aż adresat go odbierze i przeczyta).

>
> I dlaczego EventQueue.Invoke... a nie SwingUtilities.Invoke... ?

SwingUtilities powstała niedawno (o ile się nie mylę to dopiero w Java
6) - a ja korzystam z tego od wersji 1.4, więc z przyzwyczajenia
podałem EventQueue. Z tego co widzę... oto jak wygląda implementacja
SwingUtilities.invokeLater:
klasa SwingUtilities:
public static void invokeLater(Runnable doRun) {
EventQueue.invokeLater(doRun);
}

Podobnie jest z #invokeAndWait... Dodali te dwie metody tam zapewne
dlatego, żeby wszystko było razem, bo SwingUtilities, jako nowa klasa,
ma więcej różnych nowych użytecznych rzeczy, więc żeby nie szukać po
klasach i łatwiej zapamiętać, zebrali wszystko do jednej.

0 new messages