| Swing a vláknové programovanie (Java) |
Obsah
Jednou zo základných požiadaviek používateľského rozhrania je jeho responsiveness, teda včasná a rýchla reakcia na používateľov vstup (teda zadávanie z klávesnice, kliky myšou atď.) Rozhranie, ktoré nedokáže reagovať včas, používateľa frustruje, pretože tempo svojej práce musí prispôsobovať počítaču a začne mať dojem, že program ho v práci zdržuje. Včas reagujúce používateľské rozhranie by malo spĺňať nasledovné požiadavky[1]:
umožniť používateľovi vidieť aktuálny stav aplikácie
dať na vedomie, že jeho vstup bol prijatý
v prípade, že aplikácia rieši dlhotrvajúcu úlohu, dávať na známosť priebežný stav a nebudiť dojem, že zamrzla
ovplyvňovať beh programu (zatvoriť ho, pozastaviť atď.).
Pri vývoji swingovských aplikácii, ktoré spĺňajú vyššieuvedené zásady, sa nevyhneme použitiu vlákien a iných vlastností konkurentného programovania. Hoci sa to na prvý pohľad môže zdať pomerne odstrašujúce (konkurentné programovanie v sebe nesie potenciál ťažko odladiteľných chýb), v skutočnosti máme k dispozícii množstvo nápomocných tried a metód, ktoré prácu uľahčia.
Pri používaní klasických konzolových aplikácií používateľ uvažuje lineárne: aplikácia si od neho postupne vyžaduje údaje, ktoré sú do nej postupne zadávané z klávesnice.
Enter username: > novotnyr New UNIX password: > ****** Retype new UNIX password: > ****** passwd: all authentication tokens updated successfully.
V prípade grafických aplikácii sa však spôsob zmýšľania mení -- a to nielen na strane používateľa, ale i na strane vývojára.
Aplikácia musí reagovať na udalosti, ktoré používateľ vyvoláva na jej ovládacích prvkoch, a v závislosti na nich aktualizovať a prekresľovať používateľské rozhranie.
Kritickým problémom sú dlhotrvajúce operácie: kým v konzolovej
aplikácii stačí vypísať „Prosím
čakajte...“ a o prekresľovanie sa starať netreba
(všetky dáta sú už totiž zobrazené), v grafickom používateľskom rozhraní
je situácia presne opačná. Ak aplikácia vykonáva tiahly výpočet, nesmie
zabudnúť na spracovávanie používateľovho vstupu a následné
prekresľovanie, pretože v opačnom prípade získa používateľ mylný pocit,
že program vytuhol a treba ho násilne ukončiť.
Kým bežná konzolová aplikácia v Jave si vystačí s jediným vláknom (tzv. main), swingová aplikácia po spustení vytvorí viacero vlákien, ktoré sú zodpovedné za prekresľovanie, vybavovanie udalostí a ich rozosielanie jednotlivým event handlerom (teda kódu obsluhy udalostí) a ďalšie nízkoúrovňové operácie. Vlákna v typickej aplikácii sú zobrazené na nasledovnom obrázku:
Zo všetkých vlákien v swingovskej aplikácii sú však najdôležitejšie dve:
Inštancie ľubovoľného swingovského okna z neho automaticky naštartujú ostatné nízkoúrovňové vlákna (AWT-Shutdown, AWT-Windows) a vlákno EDT.
najdôležitejšie vlákno, ktoré je zodpovedné za spracovávanie udalostí a delegovanie udalostí pre ich obsluhu, ktorá sa nachádza v jednotlivých komponentoch.
Predstavme si kód z jednoduchej aplikácie, kde vytvoríme tlačidlo
JButton a priradíme mu obsluhu udalosti, ktorá
nastane po kliknutí naň: Kód v rámci metódy
actionPerformed() predstavuje obsluhu
udalostí (event
handler).
Čo sa stane po kliknutí myšou?
Vlákno EDT v sebe obsahuje tzv. rad udalostí
(event queue). Akákoľvek akcia, ktorú
vyvolá používateľ v používateľskom rozhraní -- či už pohyb myšou,
kliknutie, stlačenie klávesy atď -- spôsobí vytvorenie objektu udalosť
(v skutočnosti potomkovia triedy AWTEvent, to
však nie je dôležité) a jeho zaradenie na koniec tohto radu.
Z jeho opačného konca EDT postupne vyberá jednotlivé objekty udalostí a rozposiela ich do jednotlivých komponentov, ktoré ich ďalej posielajú do svojich event handlerov, resp. listenerov. Tu však platí dôležitá a kritická zásada: objekt udalosti je z radu vybratý až potom, čo dobehne kód v event handleri (listeneri), ktorý obslúži predošlú udalosť.
Z tejto dôležitej zásady vyplýva prvé dôležité pravidlo pri práci so Swingom:
V EDT nikdy nevykonávajte dlhotrvajúce operácie!
Ukážme si príklad, čo sa stane, ak to nedodržíme. Predstavme si kód v listeneri tlačidla, ktorý vykonáva dlhotrvajúcu operáciu, napríklad výpočet najväčšieho prvočísla.
JButton button = new JButton("Hello!"); button.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { /* dlhotrvajúci výpočet... */ stiahniSúbor("http://en.wikipedia.org/wiki/File:Java_logo.svg"); } }); add(button);
Po kliknutí na príslušný gombík sa do radu udalostí zaradí nová
inštancia udalosti ActionEvent. Po spracovaní
udalostí, ktoré sú pred ňou, sa dostane na začiatok radu. EDT ju z radu
vyberie, a pošle príslušnému komponentu (teda gombíku
button), ktorý ju odošle do svojho action listenera.
Ten vykoná kód vo svojej metóde
actionPerformed(), a po jeho dobehnutí umožní
vláknu EDT spracovať ďalšie udalosti vo fronte.
Kód v tejto metóde však predstavuje kritický bod: kým beží vyhľadávanie najväčšieho prvočísla, nemôže prebiehať spracovanie udalostí vo rade. Z toho vyplýva doslova viditeľný dôsledok: používateľské rozhranie „vytuhne“. Ak je pozastavené spracovanie udalostí, neprebieha ani prekresľovanie používateľského rozhrania a používateľ začne byť nervózny.
Predošlý obrázok ukazuje príklad zamrznutého používateľského rozhrania. Používateľ stlačil tlačidlo, začal sa vykonávať action listener, ale prekresľovanie ustalo -- rozbehne sa až po dobehnutí kódu v listeneri. Všimnime si negatívny dôsledok: tlačidlo ostalo zobrazené v medzistave, teda v stave „používateľ stlačil tlačidlo myši“. Nervózny používateľ zrejme začne „zbesilo“ klikať do okna, prípadne sa snažiť ukončiť aplikáciu, lenže jeho pokusy budú márne. Každý klik, či posun myši len vyvolá ďalšiu udalosť, ktorá sa zaradí na koniec radu udalostí. Ich spracovanie je však pozastavené; čaká sa totiž na dobehnutie action listenera.
Morálne ponaučenie sme už spomenuli: dlhotrvajúce operácie nesmú prebiehať v EDT, inak zablokujú spracovávanie udalostí. Z toho však vyplýva ešte jeden dôsledok, ktorý budeme musieť brať do úvahy v druhej časti pravidiel pre prácu so Swingom:
Kód v listeneroch komponentov vždy beží v rámci vlákna EDT.
Jedným z tradičných spôsobov, ktorými možno riešiť beh
dlhotrvajúcich úloh bez toho, aby sme narušili prekresľovanie
používateľského rozhrania, je použitie triedy
SwingWorker.
SwingWorker je veľmi flexibilná trieda,
ktorá umožňuje elegantne riešiť typické úlohy pri práci s viacerými
vláknami v Swingu.
spustenie dlhotrvajúcej úlohy bez zablokovania radu EDT a teda zastavenia prekresľovania používateľského rozhrania
podpora dlhotrvajúcich úloh, ktoré vracajú výsledok
priebežná aktualizácia používateľského rozhrania
možnosť zistiť, či úloha ešte stále beží alebo či už bola dokončená
zrušenie behu dlhotrvajúcej úlohy
Táto trieda je od JDK 6 priamo k dispozícii. Pre predošlé verzie Javy je dostupná v podobe dodatočného projektu zo stránok dev.java.net. Ak chceme vytvoriť SwingWorker, ktorý spustí sťahovanie súboru mimo vlákna EDT, vieme použiť nasledovnú konštrukciu:
SwingWorker<Void, Void> swingWorker = new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
stiahniSúbor(
"http://en.wikipedia.org/wiki/File:Java_logo.svg");
return null;
}
};
swingWorker.execute();
Základom filozofie je vytvoriť novú inštanciu
SwingWorkera. Táto trieda používa dva generické
parametre, ktoré pôsobia pomerne šokujúco, ale umožňujú dodržať typovú
kontrolu. Prvý parameter v generiku určuje návratovú hodnotu metódy
doInBackground()(V našom prípade nevracia nič).
Druhý parameter určuje dátový typ objektov prehadzovaných medzi metódami
publish() a process().
O oboch metódach sa podrobnejšie zmienime v ďalších sekciách.
Po vytvorení inštancie potrebujeme prekryť niektoré z metód, a
nakoniec spustiť vykonávanie pomocou metódy
execute().
Kód v metóde doInBackground() sa spustí
asynchrónne, teda v samostatnom vlákne mimo EDT, čo presne spĺňa zásadu
o neblokovaní prekresľovania. Návratový typ tejto metódy sa musí
zhodovať s generickým typom uvedeným pri konštrukcii
SwingWorkera. V prípade, že nevraciame žiadny
objekt, môžeme s výhodou využiť dátový typ Void
(s veľkým „V“!). Kvôli syntaktickým obmedzeniam však i v tomto prípade
musíme vrátiť nejakú návratovú hodnotu, teda null.
Vytváranie a spustenie SwingWorkera, ktoré
vykoná dlhotrvajúcu operáciu, je možné vložiť do kódu metódy
actionPerformed() v action
listeneri.
V prípade, že je SwingWorker komplexnejší a
plánuje sa jeho použitie v rámci volania action listenera, môžeme
vytvoriť samostatnú triedu, ktorá bude dediť od
SwingWorkera a implementovať interfejs
ActionListener:
public class DownloadActionListener extends SwingWorker<Void, Void>
implements ActionListener
{
private URL url;
public DownloadActionListener(String url) {
try {
this.url = new URL(url);
} catch (MalformedURLException e) {
throw new IllegalArgumentException(
"Illegal protocol in URL.", e);
}
}
public DownloadActionListener(URL url) {
this.url = url;
}
protected Void doInBackground() throws Exception {
// dlhý výpočet
return null;
}
public void actionPerformed(ActionEvent e) {
new DownloadActionListener(url).execute();
}
}Metóda actionPerformed() následne
vytvorí novú inštanciu SwingWorkera a spustí ju,
teda je ekvivalentná volaniu metódy execute(). Pridanie asynchrónnej
akcie do tlačidla je potom jednoduché:
button.addActionListener(
new DownloadActionListener(
"http://en.wikipedia.org/wiki/File:Java_logo.svg"))
Táto technika je prehľadná, hoci má drobnú nevýhodu vo
viacnásobnom vytváraní inštancie SwingWorkera
(keďže s každým volaním metódy
actionPerformed() sa vytvárajú nové inštancie). Žiaľ, iný
spôsob nie je možný, lebo inštanciu
SwingWorkera nemožno spúšťať opakovane
(podrobnosti pozri sekcia Oddiel 3.3, “Opakované úlohy”).
Kód pre preberanie súboru má jednu značnú nevýhodu: používateľ
vonkoncom netuší, v akom stave je jeho priebeh, pretože používateľské
rozhranie nedáva na známosť, či preberanie ešte stále beží alebo už
skončilo. SwingWorker dáva k dispozícii metódu
done(), ktorú možno prekryť a realizovať v nej
kód, ktorý sa má vykonať po dobehnutí operácie v
doInBackground(). Typickým príkladom je
aktualizácia používateľského rozhrania, napr. zmenou textu v nejakom
popisku label.
public class DownloadActionListener extends SwingWorker<Void, Void>
implements ActionListener
{
private JLabel label;
public DownloadActionListener(String url, JLabel labe) {
//...
}
// ...
protected void done() {
label.setText("Hotovo.");
}
}Kým kód v metóde doInBackground()
beží asynchrónne, metóda done() sa naopak
vykonáva v rámci EDT. Kód v nej by mal prebehnúť čo najrýchlejšie,
aby nebrzdil prekresľovanie.
Alternatívny spôsob využíva možnosť sledovania zmien
vlastností (properties) v JavaBeanoch.
SwingWorker umožňuje sledovať zmeny svojich
properties, a jednou z nich je status. Ak
zaregistrujeme na SwingWorkeri inštanciu triedy
PropertyChangeListener, kde zistíme, či vlastnosť
status má hodnotu DONE, vieme následne
aktualizovať používateľské rozhranie.
swingWorker.addPropertyChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
if ("state".equals(event.getPropertyName())
&& SwingWorker.StateValue.DONE == event.getNewValue()) {
label.setText("Hotovo.");
}
}
});Tento druhý spôsob umožňuje striktnejšie oddeliť
funkcionalitu SwingWorkera od samotných
komponentov používateľského rozhrania, hoci na úkor
komplexnosti.
Kód v obsluhe udalostí (teda v listeneroch) vždy beží vo vlákne EDT. Túto vlastnosť možno využiť pri modifikovaní stavu komponentov.
Metóda done() slúži na notifikáciu
ukončenia úlohy. Ako však informovať o priebežnom stave? Zoberme si kus
kódu, ktorý začne sťahovať dáta z webovej adresy:
protected Void doInBackground() throws Exception {
//...
InputStream stream = ...
BufferedOutputStream out = ...
int b = -1;
int progress = 0;
while((b = stream.read()) != -1) {
out.write(b);
// priebežne aktualizujeme UI
aktualizujUI(progress, contentLength);
}
// ...
}Po zapísaní bajtu môžeme aktualizovať používateľské
rozhranie -- napríklad môžeme využiť „teplomer“ v podobe
JProgressBar, ktorý dokáže zobraziť percentuálny
stav úlohy. Ak poznáme dĺžku sťahovaného súboru, percentuálny progres
vieme odvodiť nasledovne:
progressBar.setValue((int) (((double) progress / (double) contentLength) * 100));Samozrejme,
to predpokladá, že SwingWorker má inštančnú
premennú s progress barom, ktorú sme doň
vopred dodali.
Pretypovávacie vúdú je potrebné kvôli celočíselnému deleniu:
delenie dvoch intov vedie k
intu, čo má dôsledky v podobe výpočtu 2
/ 3 == 0. Metóda aktualizujUI() by
mohla vyzerať nasledovne:
private void aktualizujUI(double progress, double length) {
int percents = (int) ((progress / contentLength) * 100);
progressBar.setValue(percents);
}
Uvedený kus kódu je na prvý pohľad korektný, ale je v ňom jedna
zásadná chyba, ktorá súvisí s prácou so swingovskými vláknami. Metóda
doInBackground(), ako už bolo viackrát
zmienené, beží v samostatnom vlákne. V tom istom vlákne bežia aj metódy,
ktorá sú z nej volané -- teda i naša metóda
aktualizujUI(), v ktorej aktualizujeme stav
progress baru. V Swingu však platí dôležitá zásada:
Stav swingovských komponentov možno meniť len z vlákna EDT!
Toto je druhá a posledná kritická zásada práce so Swingom. Jej ignorovanie nevedie k syntaktickým chybám, a aplikácia zrejme pobeží. Skôr či neskôr, a hlavne v kritických situáciách, sa môže prejaviť nečakaným správaním, ktoré sa môže prejaviť nesprávnym prekresľovaním komponentov či iným čudným správaním.
Metóda aktualizujUI() túto zásadu
ignoruje, pretože nebeží v rámci EDT. Ako to však opraviť?
V samotnej podstate tu ide o problém odovzdávania dát medzi
vláknami a ich koordinácie. V bežnom konkurentnom programovaní je to
nepríliš ľahká úloha. SwingWorker však poskytuje
elegantnú možnosť, ako to dosiahnuť bez väčších ťažkostí.
V Swingu existuje niekoľko málo metód komponentov, ktoré sú thread-safe, teda možno ich bezpečne volať aj z iného vlákna než EDT. Je však smutným faktom, že i v prípade, že je v dokumentácii uvedená thread-safety, realita tomu nezodpovedá. Z tohto dôvodu je lepšie vždy a všade dodržiavať zásadu o modifikácii komponentov len z vlákna EDT, aj keď to podľa dokumentácie nie je nutné. V konečnom dôsledku to však nie je až také obmedzenie, keďže počet dokumentovaných thread-safe metód je pomerne malý.
Základnou metódou, ktorou možno odoslať dáta do vlákna EDT, je
publish(), ktorá umožňuje odoslať do EDT dáta
uvedené v jej parametroch. Dátový typ parametra je špecifikovaný v
druhom generickom argumente uvedenom pri vytváraní inštancie
SwingWorkera.
Dáta, ktoré sme odoslali do EDT použitím
publish(), si môžeme vyzdvihnúť v rámci
metódy process(). Tá beží v rámci vlákna EDT,
a teda je vhodným miestom, kde môžeme aktualizovať komponenty bez
toho, aby sme porušili vyššieuvedenú zásadu. Táto metóda má svojskú
signatúru:
protected void process(List<Integer> chunks)
Jej
parametrom je zoznam, ktorý obsahuje prvky toho typu, ktorý bol
deklarovaný v generickom argumente, resp. v parametri metódy
publish(). V zozname sa ocitnú všetky
publikované dáta. Za normálnych okolností by sa
dalo čakať, že každé volanie publish() je
spárované s volaním process(), ale kvôli
efektivite volaní môže SwingWorker zlúčiť
niekoľko publikovaných dát do jedného volania metódy
process(). Prirodzene, žiadne publikované
dáta sa nestratia -- objavia sa v zozname chunks v
takom poradí, v akom boli publikované do EDT.
V dokumentácii sa udáva príklad zoskupovania viacerých volaní:
publish(123); publish(860); publish(2320);
Tieto volania môžu vyústiť v jediné volanie
process(), kde bude zoznam chunks
obsahovať prvky 123, 860 a 2320.
Metóda publish() tiež podporuje
odosielanie viacerých parametrov naraz. V niektorých situáciách
môžeme teda odoslať rovno publish(123, 860, 2320) --
samozrejme, v našom príklade sledovania priebehu to nemá
zmysel.
Ak sledujeme priebeh, bude nás zaujímať len posledná publikovaná hodnota, teda posledný prvok zoznamu. V rámci metódy vypočítame percento priebehu a nastavíme ho na zozname.
protected void process(List<Integer> chunks) {
double progress = chunks.get(chunks.size() - 1);
progressBar.setValue((int) (progress / contentLength) * 100));
}
Ak chceme priebežne sledovať percentuálny priebeh úlohy, môžeme
tiež použiť alternatívny spôsob, ktorý využíva metódu
setProgress() zabudovanú v SwingWorkeri. Má
jeden parameter v intervale 0..100, ktorý vieme nastaviť
property s názvom
progress.
protected Void doInBackground() throws Exception {
//...
while((b = stream.read()) != -1) {
// .. priebežne publikujeme progres
setProgress(percentProgress);
}
// ...
}Zmenu stavu možno sledovať zaregistrovaním inštancie
PropertyChangeListener na inštancii
SwingWorkera.
V niektorých prípadoch chceme vykonať dlhotrvajúcu operáciu tak,
aby používateľ musel vyslovene počkať na výsledok. Chceme mu zabrániť v
používaní grafického rozhrania, ale zároveň chceme predísť vytuhnutiu
aplikácie. Dokumentácia k SwingWorkeru v tomto
prípade radí trik, ktorý využíva modálne okno.
Vo vlákne EDT vytvoríme inštanciu modálneho okna, spustíme
SwingWorker a toto okno (modálne okno „vyblokuje“
používateľské rozhranie). Zároveň však budeme v property
change listeneri priebežne sledovať, či ešte operácia
beží. Ak už dobehla, okno zatvoríme. Zmienený property
change listener vyzerá nasledovne a podobá sa na
listener zo sekcie Oddiel 2.2, “Zistenie, či úloha dobehla”.
private final class SwingWorkerCompletionWaiter
implements PropertyChangeListener
{
private JDialog dialog;
public SwingWorkerCompletionWaiter(JDialog dialog) {
this.dialog = dialog;
}
public void propertyChange(PropertyChangeEvent event) {
if ("state".equals(event.getPropertyName())
&& SwingWorker.StateValue.DONE == event.getNewValue()) {
dialog.setVisible(false);
dialog.dispose();
}
}
}
Použitie na príklade action listenera je nasledovné:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
SwingWorker<Void, Void> worker = ...
JDialog dialog = new JDialog(MainForm.this, true);
worker.addPropertyChangeListener(
new SwingWorkerCompletionWaiter(dialog));
worker.execute();
dialog.setVisible(true);
}
});V tomto prípade treba dať pozor na správne poradie
príkazov. Do workera treba vložiť
property change listener, ktorý však
potrebuje inštanciu modálneho JDialogu. Metóda
execute() spustí asynchrónne
SwingWorker, čo sa musí udiať ešte pred
zobrazením modálneho okna (v opačnom prípade okno blokuje EDT).
Vyššie sme sa zmieňovali o metóde
execute(), ktorá asynchrónne spustí kód v metóde
doInBackground(). V útrobách
SwingWorkera sa udržiava množina obslužných
vlákien (typicky desiatich), ktorá automaticky obsluhuje všetky
workerovské úlohy.
Napriek tomu však existujú situácie, keď si chceme spúšťanie
SwingWorkera spravovať sami.
SwingWorker je možné použiť v role bežnej
asynchrónne spúšťanej úlohy. Trieda implementuje interfejs
RunnableFuture, čím dokáže zároveň splniť
rolu Runnable i
Future.
Implementovaním interfejsu Runnable
je umožnené spúšťanie inštancie SwingWorkera
pomocou klasickej triedy vlákna Thread.
Nasledovný kód spustí vo vlákne Thread inštanciu
workera presne tak, ako akúkoľvek inú
inštanciu Runnable:
SwingWorker<Void, Integer> worker = ... Thread thread = new Thread(worker); thread.start();
Tento kód je zhruba ekvivalentný štandardnému
spusteniu cez worker.execute(). V tomto prípade však
explicitne určíme vlákno, ktoré bude spracovávať danú úlohu.
Za normálnych okolností nie je veľmi dôvod používať túto metódu.
Keďže SwingWorker implementuje
Runnable, možno ho zaslať do exekútora.
Táto možnosť má zmysel napr. v prípade, že chceme synchronizovať beh
viacerých vlákien. Predstavme si, že chceme zobraziť okno, ktoré má
viacero progress barov indikujúcich
paralelné sťahovanie viacerých súborov a po dokončení sťahovania sa
automaticky zatvorí.
Tento problém vieme elegantne vyriešiť pomocou exekútora, teda
inštancie triedy ExecutorService. Doňho vieme
odoslať viacero úloh, ktoré sa spustia asynchrónne v samostatných
vláknach. V tomto konkrétnom prípade navyše vieme využiť metódu
invokeAll(), ktorá blokuje dovtedy, kým
nedobehnú všetky úlohy. Inak povedané, spustíme úlohy, blokujeme a po
ich dobehnutí zatvoríme okno. Drobným nedostatkom je fakt, že
invokeAll() vyžaduje úlohu v podobe inštancie typu
Callable. Našťastie, volaním
Executors.callable() vieme previesť
Runnable na
Callable.
setVisible(true);
try {
ExecutorService executor = Executors.newCachedThreadPool();
List<Callable<Object>> tasks
= new LinkedList<Callable<Object>>();
for (Component c : this.getContentPane().getComponents()) {
if (c instanceof DownloadProgressPanel) {
// panel s progress barom a popiskom
DownloadProgressPanel panel = (DownloadProgressPanel) c;
// vytvoríme inštanciu SwingWorkera
DownloadTask task
= new DownloadTask(panel.getUrl(),
panel.getProgressBar());
// prevedieme ho na Callable a zaradíme do zoznamu úloh
Callable<Object> callable = Executors.callable(task);
tasks.add(callable);
}
}
// vykonáme všetky úlohy. Volanie metódy čaká, kým nedobehnú úlohy.
executor.invokeAll(tasks);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
setVisible(false);
}
SwingWorker je skutočne rafinovaná trieda,
ktorou dokážeme vyriešiť veľa problémov. Napriek tomu sa môže v
zriedkavých prípadoch stať, že si potrebujeme poradiť aj bez neho.
Príkladom môže byť kód vykonávaný v rámci vlákna, ktorý chce aktualizovať
swingovské komponenty. Pravidlo v Swingu tvrdí, že to možno robiť len v
rámci EDT, čo sa v rámci SwingWorkera dialo v
metóde process(), resp.
done().
Nízkoúrovňové volanie využíva metódu
SwingUtilities.invokeLater(), ktorá vie zobrať parameter typu
Runnable, predstavujúci úlohu. Po zavolaní
tejto metódy sa úloha Runnable zaradí na
koniec radu v EDT, čo zaručí vykonanie kódu vo vlákne EDT a teda možnosť
meniť komponenty. Úloha potom beží asynchrónne.
Podotknime, že zavolanie metódy
invokeLater() môžeme vykonať z ľubovoľného vlákna
(aj z EDT). V prípade, že ho voláme z vlákna EDT, sa kód v úlohe
Runnable vykoná až potom, čo sa spracujú ostatné
úlohy čakajúce vo fronte EDT. Niekedy sa oplatí vedieť, či vykonávaný kód
beží v EDT alebo nie, čo zistíme nasledovne:
boolean smeVoVnutriEDT = SwingUtilities.isEventDispatchThread()
Doplnkom metódy invokeLater() je
SwingUtilities.invokeAndWait(), ktorý funguje rovnako, ale
blokuje do chvíle, kým nedobehne úloha odoslaná do EDT.
Programovanie svižného používateľského rozhrania v Swingu má svoje
úskalia. Trieda SwingWorker ich však v mnohom
dokáže maximálne uľahčiť. Najdôležitejšou zásadou je nezabúdať na niekoľko
zásad. Žiaľ, ak ich nedodržíme, žiadne IDE nás na to neupozorní a už zo
povahy konkurentného programovania vyplýva, že chyby, ktoré z toho
vyplynú, sa ladia extrémne ťažko.
Dlhotrvajúce úlohy vykonávajte mimo EDT!
Modifikácia komponentov sa musí udiať v rámci EDT!
Netreba však zabúdať ani na bočné zásady:
Kód v listeneroch komponentu beží v rámci EDT.
SwingWorker možno spustiť len
raz.
Tabuľka 1. Sumár metód SwingWorkera
| Metóda | Význam | Beží v |
|---|---|---|
new SwingWorker<V, W> | V: návratový typ
done(), W: parameter v
publish() | V rámci vlákna, z ktorého sa volal tento konštruktor. |
doInBackground() | Umiestnenie kódu, ktorý má bežať asynchrónne mimo EDT. | mimo EDT |
done() | kód sa vykoná po dobehnutí metódy doInBackground() | EDT |
publish(W...) | odošle parameter typu W do EDT | - |
process(List<W>) | spracováva výsledky odoslané z metódy
publish() | EDT |
Swing Threads, článok na mindprod.com
Threads and Swing, článok o zásadách používania vlákien v Swingu
Swing Threads, článok na mindprod.com: