Úvod
V předchozím díle jsem se věnoval WPF jen velmi obecně. Tento článek má za úkol představit objektovou architekturu této technologie, jejíž znalost je nezbytným základem pro pochopení všech hlubších principů.
Dispatcher
K čemu Dispatcher slouží?
Dispatcher je hnacím motorem celého WPF. Jedná se o komponentu, která je principiálně poměrně jednoduchá. Ve zkratce je to fronta úkolů zajišťující, že se úkoly vykonávají po sobě v rámci jednoho hlavního vlákna.
Obecně k uživatelskému prostředí v systému Windows zpravidla přistupujeme vždy z jednoho vlákna. Takové vlákno se nazývá “hlavní vlákno UI” (UI = user interface = uživatelského prostředí). A právě vše, co se v tomto hlavním vlákně bude vykonávat řídí onen Dispatcher. Takový přístup nazýváme single-treaded (jedno-vláknový). Je zde jak z historického hlediska, tak z hlediska čistě praktického.
Díky tomuto single-threaded modelu se nemusíme starat o celou řadu synchronizačních problémů. Pokud chceme, aby se v rámci uživatelského prostředí cokoliv stalo, přidáme úkol do Dispatcheru a on sám zajistí jeho spuštění v hlavním vlákně UI a ve správný čas. Máme tak jistotu, že se nebudou nikdy vykonávat dva úkoly týkající se uživatelského prostředí současně. Mezi tyto operace patří hlavně:
- vykreslování
- události uživatelského vstupu (kliknutí na tlačítko, stisk klávesy atp.)
- změny vlastností (změna textu nadpisu okna, přidání položky do seznamu)
- a obecně cokoliv, co předáme Dispatcheru do fronty událostí
Výhodou je, že o Dispatcheru nemusíte vůbec vědět, pokud ho nepotřebujete. Je to vnitřní princip WPF a vás v zásadě nemusí zajímat, že kliknutí na tlačítko znamená, že systém Windows pošle událost, která se přeloží na kliknutí a přidá do fronty Dispatcheru. Ten ji, hned jak to bude možné, vyvolá. Zjistí se, na který prvek uživatel kliknul a vyvolá jeho událost. My totiž pohodlně uvnitř této událost vykonáme potřebný kód a opět nás příliš nezajímá, že po jeho dokončení se Dispatcher opět ujme kontroly nad vláknem a čeká na další úkoly, popřípadě vykoná úkoly, které se mezitím nashromáždily.
“Vytuhnutí” single-threaded aplikací
První nevýhodu, na kterou narazíme (nebo spíš důsledek špatného použití) je stav, kdy některá operace v Dispatcheru bude trvat příliš dlouho dobu. V takovém případě celé uživatelské prostředí na dobu provádění operace lidově řečeno “vytuhne”. Dispatcher totiž bude s ostatními úkoly čekat na dokončení blokujícího úkolu. Například událost po stisknutí tlačítka na formuláři bude trvat 10 vteřin. A v tomto čase před dokončením neproběhne žádná událost reagující na vstup od uživatele, ani se žádným způsobem uživatelské prostředí nepřekreslí. Aplikace je tedy po tuto dobu “mrtvá”. Po dokončení operace se opět začnou provádět všechny čekající události a aplikace je opět ovladatelná. Pro uživatele je ale tento stav nepříjemný.
Základní princip aplikací, které reagují na vstupy od uživatelů i v průběhu těchto úkolů je vytvořit samostatné vlákno. Zde je ale důležité podotknout, že z toho samostatného vlákna nemůžeme ovládat uživatelské prostředí. Pokud to uděláme, příkaz skončí s chybou. Je to bezpečnostní mechanismus, který se má zabránit, aby do uživatelského prostředí zasáhlo jiné, než hlavní vlákno UI.
Pokud tedy chceme, aby výsledek operace (který ovlivní nějakým způsobem uživatelské prostředí) proběhl tak, jak je zamýšleno v single-threaded modelu, musíme tento úkol nechat provést právě hlavním vláknem UI. A to tak, že ji z pomocného vláka zaúkolovaného k vykonání dlouhé operace přidáme do úkolů Dispatcheru. Ten se o spuštění v hlavním vlákně postará.
Když tedy použití Dispatcheru shrnu, je důležité, aby operace, které se vykonávají v rámci hlavního vlákna netrvaly příliš dlouho. Po dobu trvání totiž aplikace nebude reagovat.
Životní cyklus Dispatcheru
Typicky je v každé WPF aplikaci jediná instance Dispatcheru. Ta se vytvoří při startu aplikace a stará se o běh hlavního vlákna. Dispatcher je zároveň také nositel informace o tom, zda se má aplikace ukončit. V tom případě opustí smyčku čekání na další úkoly a hlavní vlákno UI se ukončí a s tím i celá aplikace.
DispatcherObject
Téměř každý objekt ve WPF, se kterým chceme pracovat jen z hlavního UI vlákna dědí z třídy DispatcherObject. Ten slouží primárně ke dvou účelům:
- Vlastnost dispatcherObject.Dispatcher - nese informaci, který Dispatcher se o tento objekt stará
- Metoda dispatcherObject.CheckAccess – metoda vrací true, pokud ji voláme z vlákna o které se stará příslušný Dispatcher
- Metoda a dispatcherObject.VerifyAccess - metoda, které vyvolá výjimku, pokud jsme v jiném vlákně, než v tom, o které se stará příslušný Dispatcher
Všechny grafické komponenty bez výjimky ve WPF dědí právě z třídy DispatcherObject. Zároveň je pravidlem, že většina jejich metod a vlastností volá metodu VerifyAccess. Tím se zajistí, že vznikne výjimka, pokud přistoupíme k metodám nebo vlastnostem těchto komponent z jiného, než hlavního vlákna UI.
Závěr
Doufám, že vás trochu kratší, teoretický úvod příliš neodradil. Věřím, že je to však důležitý základ, který pomůže objasnit fungování vašich WPF aplikací. V následujícím díle se vrátíme k návrhu uživatelského prostředí – jazyka XAML.