Více vláknové aplikace jsou tématem, do kterého se čtenáře budu snažit uvést v tomto díle seriálu. Objasníme si co to vlastně vlákna jsou, proč se používají a uvedeme si jejich základní použití v prostředí .NET.
Vlákna? O co jde?
Žijeme v době, kdy současné operační systémy umožňují běh dvou a více programů najednou a uživatelům na to dnes asi nic zvláštního nepřipadne. A tak vás možná při psaní vašich programů napadlo, zda – li je možné učinit něco takového, co dělá operační systém s programy i v naší aplikaci s jednotlivými operacemi.
Odpověď zní jednoznačně ANO, něčeho takového jsme opravdu schopni v naší aplikaci dosáhnout. Rozdělit některé operace prováděné v rámci běžícího programu, je možné pomocí takzvaných „light-weight“ procesů, které jsou v terminologii prostředí .NET, stejně jako v jazyku Java, označeny jako vlákna.
Použitím vláken je nám tedy umožněno vytvořit aplikaci, kde budou některé operace prováděny paralelně. Ono použité slovo paralelně není ve většině případů pravdivé. Ve skutečnosti je to tak, že je čas procesoru jednotlivým vláknům, které provádějí konkrétní operace, předáván střídavě, ale s takovou rychlostí, že v lidském měřítku se prováděné operace skutečně jeví jako paralelní. Ale abychom byli terminologicky správní měli bychom pro provádění operací užitím vláken místo slova paralelní použít slovo pseudo-paralelní.
Samozřejmě, ne všechny aplikace potřebují pro svůj běh použití vláken. Spíše je to naopak. Velká část programů je schopna reagovat operacemi na podněty uživatele dostatečně rychle a proto by použití vláken nepřineslo užitek. Užitek nám vlákna přinesou v opačných situacích, tedy v situacích, kdy jsou prováděné operace natolik výpočetně (a tím pádem i časově pro procesor) náročné, kdy by uživatel musel na dokončení operace čekat.
V takovýchto případech se použijí vlákna například tak, že jedno vlákno vykonává operace s daty získanými uživatelem a druhé vlákno je určeno pro získávání dalších uživatelských vstupů do aplikace. Toto je samozřejmě jeden z mnoha případů smysluplného použití techniky vláken.
Jedním z dalších známých příkladů na použití vláken je čekání na uživatelské vstupy, kdy uživatel zadává nějaké vstupy, což je z pohledu počítače velmi pomalá operace (čas procesoru není zdaleka plně využit) a mezitím další vlákno tyto vstupy již nějakým způsobem zpracovává (známá kontrola pravopisu z textových procesorů). A v případě komplexnějších aplikací je někdy spuštěno hned několik vláken zajišťujících specifické operace a tyto vlákna spolu kooperují.
Použití vláken v .NET
Po úvodním vysvětlení smyslu vlákno a jeho smyslu při programování nyní přejdu k tvorbě více vláknových aplikací v prostředí .NET. Pokud budeme chtít učinit naši aplikaci více vlákenní, budeme k tomu potřebovat elementy z jmenného prostoru System.Threading. Nejzajímavější třídou ze zmíněného jmenné prostoru je třída Thread, jejíž instance slouží k reprezentaci jednotlivých vláken. Zdrojový kód, který následuje, ukazuje použití příklad, ve kterém je vytvořeno a spuštěno další vlákno.
/// <summary>
/// Uvodni priklad na vyuziti vlaken
/// </summary>
public static void SpustPriklad()
{
//pomoci delegata asociujeme metodu, ktera bude vlaknem spustena
ThreadStart lStartInfo = new ThreadStart(Bez);
//vytvoreni dalsiho vlakna
Thread lThread = new Thread(lStartInfo);
//spusteni dalsiho vlakna
lThread.Start();
//zavolani metody z aktualniho vlakna
Bez();
}
static void Bez()
{
for (char znak = `a`; znak <= `z`; znak++)
{
Console.Write(znak);
}
}
K určení metody, která bude spuštěna v nově vytvářeném vlákně slouží delegát ThreadStart, který předepisuje metodu bez návratové hodnoty a bez vstupních formálních parametrů. Instanci tohoto delegáta, který je v případě našeho příkladu asociován s metodou Bez následně předáme konstruktoru třídy Thread. Tím, že vytvoříme instanci třídy Thread, jsme ještě nové vlákno nespustili.
K tomu, abychom vlákno spustili, slouží instanční metoda Start. Hned po spuštění nově vytvořeného vlákna, zavoláme metodu Bez i z aktuálního vlákna, tedy z vlákna, které je jako jediné spuštěno s vytvořením aplikační domény našeho programu. Po dokončení běhu tohoto ukázkového programu byste měli vidět výstup podobný tomuto:
aabcdefghijklmnopqrstuvwxyzbcdefghijklmnopqrstuvwxyz
Jak můžeme vidět, tak střídání výstupů jednotlivých vláken není ani zdaleka pravidelný. Je tomu tak z důvodu, že je bez jakékoli synchronizace vláken přistupováno ke sdílenému prostředku, kterým je v našem případě výstupní proud systémové konzole.
Celkem zajímavého výsledku dosáhneme modifikací prvního příkladu do následující podoby:
/// <summary>
/// Priklad na pouziti vlaken s vypisem jmen
/// jednotlivych vlaken
/// </summary>
public static void SpustPrikladSNazvy()
{
//pomoci delegata asociujeme metodu, ktera bude vlaknem spustena
ThreadStart lStartInfo = new ThreadStart(Bez);
//aktualnimu vlaknu priradime jmeno
Thread.CurrentThread.Name = "V1";
//vytvoreni dalsiho vlakna
Thread lThread = new Thread(lStartInfo);
//prirazeni jmena novemu vlaknu
lThread.Name = "V2";
//spusteni dalsiho vlakna
lThread.Start();
//zavolani metody z aktualniho vlakna
Bez();
}
static void BezSNazvy()
{
for (char znak = `a`; znak <= `z`; znak++)
{
Console.Write("[" + Thread.CurrentThread.Name + "]:" + znak);
}
}
V příkladu jsme využili instanční vlastnost třídy Thread, kterou je vlastnost Name, představující název konkrétního vlákna, takže nyní obdržíme výstup podobný tomuto:
[V1]:a[V1]:b[V1]:c[V1]:d[V1]:e[V2]:a[V2]:b[V2]:c[V2]:d[V2]:e[V2]:f[V2]:g[V2]:h[V
2]:i[V2]:j[V2]:k[V2]:l[V2]:m[V2]:n[V2]:o[V2]:p[V2]:q[V2]:r[V2]:s[V2]:t[V2]:u[V2]
:v[V2]:w[V2]:x[V2]:y[V2]:z[V1]:f[V1]:g[V1]:h[V1]:i[V1]:j[V1]:k[V1]:l[V1]:m[V1]:n
[V1]:o[V1]:p[V1]:q[V1]:r[V1]:s[V1]:t[V1]:u[V1]:v[V1]:w[V1]:x[V1]:y[V1]:z
Zajímavé je, že i přestože bylo spuštění druhého vlákna zavoláno dříve než volání metody BezSNazvem z aktuálního vlákna, tak několik prvních výstupů není zapříčiněno druhým vláknem, ale naopak vláknem aktuálním, tedy dříve než je druhé vlákno plně spuštěno.
V dalším díle se budeme opět zabývat problematikou vláken a to konkrétné možností předat vláknu nějaké parametry.