Integrované prostředí rubriky Visual Basic |
Autor: Ján Hanák |
||||||||
|
Téma měsíce |
||||||||
Programování vícevláknových aplikací |
|||||||||
Časová náročnost (min): 65 |
Začátečník |
Pokročilý |
Profesionál |
||||||
|
|
|
|||||||
|
Použitý operační systém : Hlavní vývojový
nástroj : Další vývojový software : Jiný software : |
Windows 2000 SP3 Visual Basic .NET
2002 Žádný Žádný |
|||||||
|
Nepřehlédněte exkluzivní
Speciál pro programátory, který
pojednává o jazykové interoperabilitě mezi jazyky Managed Extensions for C++
a Visual Basic .NET. |
||||||||
|
Milí čtenáři,
32bitové prostředí operačního systému Windows s sebou přineslo několik podstatných a zásadních změn, a to nejen z uživatelského, nýbrž i z programovacího pohledu. Na přední příčku v řebříčku „top“ změn se řadí také plná podpora multitaskingu (paralelního zpracování většího množství úloh) a multithreadingu (podpora exekuce vícevláknových aplikací). Jak naprogramovat vícevláknovou aplikaci se dozvíte právě v tomto vydání sekce Téma měsíce.
Obsah |
Teoretický úvod do problematiky procesů a vláken
Abychom mohli mluvit o programování vícevláknových aplikací na patřičné úrovni, musíme si nejprve povědět pár slov k „vícevláknové“ terminologii. Začneme od definice pojmu aplikace, který je vám pravděpodobně nejbližší. Aplikace je definována jako soubor programového kódu, dat a dalších interních nebo externích zdrojů, které společně vytvářejí jeden celek, jenž řeší jistou smysluplnou úlohu. Pojem aplikace se někdy volně zaměňuje s termínem počítačový program.
|
I když je ve většině případů tato substituce možná, není zcela přesná.
A to z toho důvodu, že programem může být také jenom několik řádků
programového kódu, které ač řeší jistý úkol, nemusí být nutně samostatným
celkem, jenž je předložen před uživatele. Můžeme však říct, že aplikace je
již hotový, odladění počítačový program, jenž se stává předmětem prodeje na
softwarovém trhu. |
V .NET prostředí je kód aplikace uložen v jedné, nebo i několika assembly. Assembly je pak uložena v přenositelném spustitelném souboru PE (Portable Executable). Jestliže je soubor aktivován (poklepáním na jeho ikonu), potřebný programový kód v podobě jazyka MSIL bude přeložen JIT kompilátorem, umístěn do paměti počítače a podroben přímé exekuci. V tomto okamžiku říkáme, že aplikace běží. V programování se běžící aplikace označuje jako proces. Pro potřeby procesu je alokován jistý paměťový prostor, ve kterém se daný proces nachází. 32bitový operační systém Windows je, jak již bylo řečeno, plně vybaven pro správu více procesů, resp. úkolů. Jak je to možné? Velmi zjednodušeně můžeme prohlásit, že každému procesu operační systém přičlení stanovený časový interval, v rámci kterého je procesor (CPU) počítače plně zaměstnán prováděním kódu daného procesu. Po uplynutí vymezeného intervalu se operační systém „přepne“ do jiného procesu a jistou dobu vykonává kód tohoto procesu. Tímto způsobem operační systém spravuje všechny procesy, neboli spuštěné aplikace. Je důležité si uvědomit, že práce s procesy je kompletně v rukou operačního systému, nikoliv jiných procesů.
Každý proces je tvořen přinejmenším jedním vláknem (thread). Vlákno lze definovat jako samostatnou jednotku exekuce programového kódu procesu. Každé vlákno má své identifikační číslo (ID), jméno a prioritu. Zatímco jméno vlákna tvoří textový řetězec, jenž slouží na uživatelskou identifikaci vlákna, identifikační číslo vlákno přesně charakterizuje z pohledu operačního systému. Priorita vlákna určuje, jak intenzivně je zapotřebí programový kód daného vlákna provádět. Zvyčejně mají vlákna normální prioritu, avšak v případě potřeby je možné zvýšit, nebo naopak snížit prioritu vlákna na příslušnou úroveň. Jestliže proces obsahuje jenom jedno vlákno, mluvíme o jednovláknové aplikaci (single threaded application). Pokud jste vyvíjeli aplikace v předchozí verzi Visual Basicu, nejspíš jste pracovali pouze s jednovláknovými aplikacemi. Visual Basic .NET ovšem nabízí programování také vícevláknových aplikací (multithreaded applications). Vícevláknová aplikace je aplikace, která používá dvě a více vláken. Komparaci jedno a vícevláknové aplikace můžete vidět na obr. 1.
Obr. 1 – Jedno a vícevláknová aplikace
Vlákno, které je na obrázku pojmenováno jako „Vlákno 1“ je základním vláknem procesu, což znamená, že toto vlákno je vytvořeno implicitně běhovým prostředím aplikace. Naproti tomu vlákno s názvem „Vlákno 2“ je pracovním vláknem, které vytvořil programátor pro své potřeby. Ačkoliv obrázek představuje jenom dvouvláknovou aplikaci, proces může obsahovat i větší množství vláken. Příliš mnoho vláken ovšem není žádoucí, protože by snadno mohly nastat potíže se synchronizací jednotlivých vláken (pomineme-li poněkud větší paměťovou náročnost aplikace).
|
Každému vláknu v procesu je přiřazen jistý časový interval, během
kterého CPU počítače realizuje programové instrukce daného vlákna. Jakmile tento
interval uplyne, CPU okamžitě začne zpracovávat kód dalšího vlákna. Jelikož
je časový interval realizace kódu vlákna velmi malý, vizuálně to vypadá,
jakoby obě vlákna pracovala současně. |
Vytváříme první vícevláknovou aplikaci
V následující programové ukázce si předvedeme, jak sestrojit jednoduchou dvouvláknovou aplikaci. Postupujte podle níže uvedených instrukcí:
Dim
NovéVlákno As New
Threading.Thread(AddressOf Metoda)
NovéVlákno.Name =
"MojeVlákno"
NovéVlákno.Start()
Pokaždé, když budeme chtít vytvořit nové vlákno, použijeme třídu Thread z jmenného prostoru System.Threading. Z tohoto pohledu je tedy zřejmé, že k vytvořenému vláknu budeme ve skutečnosti přistupovat prostřednictvím instance třídy Thread. Instance této třídy se ovšem rodí poněkud zvláštním způsobem. Podívejme se na něj blíže. Především je zapotřebí obsloužit parametrický konstruktor třídy Thread, kterému je nutno předat (pomocí operátoru AddressOf) runtime adresu procedury, která bude prováděna na nově vytvořeném vlákně. Ve skutečnosti je však celý proces poněkud komplikovanější: Ačkoliv je tato skutečnost zastíněna, je vytvořen delegát ThreadStart, jemuž je svěřena runtime adresa spouštěcí procedury. I když vytvoříme nové vlákno (resp. instanci třídy Thread), neznamená to ještě, že jsme také toto vlákno spustili.
|
V této souvislosti je nutno podotknout, že při práci s vlákny je
zapotřebí důsledně rozlišovat dva pojmy, a sice vytvoření vlákna a jeho
spuštění. Vlákno je vytvořeno jakmile je zrozena instance třídy Thread.
Takto vytvořené vlákno má příznak Unstarted, což znamená, že ještě
nebylo spuštěno. Aby mohlo být vlákno spuštěno, musí být zavolána metoda Start
instance třídy Thread. |
Spuštění nového vlákna zabezpečíme zavoláním metody Start instance třídy Thread. Jakmile je aktivována metoda Start, je zavolána spouštěcí procedura, která bude prováděna na novém vlákně. Proces tvorby a spuštění nového vlákna je znázorněn na obr. 2.
Obr. 2 – Proces vytvoření a spuštění vlákna
|
Pro zvídavé programátory: Vytvoření
instance třídy Thread pod drobnohledem |
Ještě jednou se podívejme na výše uvedený zdrojový kód pro vytvoření
instance třídy Thread: Dim NovéVlákno As New Threading.Thread(AddressOf
Metoda)
NovéVlákno.Name = "MojeVlákno"
NovéVlákno.Start() I když je tento kód zcela správný, není v něm vidět role delegáta
ThreadStart. Obměníme-li kód následovně, vyjdou skryté skutečnosti na
povrch: Dim NovéVlákno As New Threading.Thread _ (New Threading.ThreadStart(AddressOf
Metoda))
NovéVlákno.Name = "MojeVlákno" NovéVlákno.Start() Tento kód jasně demonstruje způsob, pomocí něhož je instanci třídy Thread
předán delegát ThreadState, kterému je prostřednictvím operátoru AddressOf
poskytnuta runtime adresa spouštěcí procedury (s názvem Metoda), která
bude realizována na nově vytvořeném vlákně. Ačkoliv je tento zápis kódu pro
vytvoření vlákna znatelně delší, umožňuje nám pohled do zákulisí procesu
vytváření vlákna. V mnoha programech jej však zcela jistě neuvidíte,
protože Visual Basic přidává uvedené rozšíření kódu implicitně, a tedy není
nutné, abyste tuto „prodlouženou“ verzi kódu používali ve svých aplikacích. |
V našem případě je vytvořena instance třídy Thread s názvem NovéVlákno. Konstruktoru třídy je předána adresa procedury Metoda, tato procedura se tedy bude provádět na druhém (vytvořeném) vlákně. Ještě předtím, než je vlákno spuštěno, je upravena vlastnost Name instance MojeVlákno – nové vlákno tak bude disponovat uživatelsky přívětivým jménem.
Option Strict On
Public Class Form1
Inherits
System.Windows.Forms.Form
Dim frm As Form
Dim
lblText1 As Label
Dim
lbltext2 As Label
Dim
Progres As ProgressBar
Const
PROGRES_MIN As Integer
= 1
Const
PROGRES_MAX As Integer
= 1000
Private
Sub Button1_Click(ByVal
sender As System.Object, _
ByVal
e As System.EventArgs) Handles
Button1.Click
Dim
NovéVlákno As New
Threading.Thread(AddressOf Metoda)
NovéVlákno.Name =
"MojeVlákno"
NovéVlákno.Start()
End
Sub
Private
Sub Metoda()
frm = New Form()
lblText1 = New Label()
lbltext2 = New Label()
Progres = New ProgressBar()
With
frm
.ControlBox = False
.FormBorderStyle =
FormBorderStyle.FixedDialog
.Text = "Probíhá
výpočet náhodných čísel..."
End
With
With
lblText1
.Location = New Point(0, 0)
.Size = New Size(frm.Width, frm.Height \ 4)
.Text = "Právě
probíhá generování náhodných čísel"
.Font = New Font("Verdana", 18, FontStyle.Bold, _
GraphicsUnit.Pixel)
.TextAlign =
ContentAlignment.MiddleCenter
End
With
With
lbltext2
.Location = New Point(0, 75)
.Size = New Size(frm.Width, frm.Height \ 6)
lbltext2.Text =
"Číselný průbeh generování čísel:"
.Font = New Font("Verdana", 11, FontStyle.Bold, _
GraphicsUnit.Pixel)
.TextAlign =
ContentAlignment.MiddleCenter
End
With
With
Progres
.Dock = DockStyle.Bottom
.Height = frm.Height \ 6
.Minimum = PROGRES_MIN
.Maximum = PROGRES_MAX
End
With
With
frm.Controls
.Add(Progres)
.Add(lblText1)
.Add(lbltext2)
End
With
AddHandler
frm.Load, AddressOf frm_Load
frm.Show()
Dim
f As Integer =
FreeFile()
Randomize()
Dim
a, x As Integer
FileOpen(f,
"d:\data.txt", OpenMode.Output)
For
a = PROGRES_MIN To PROGRES_MAX
x = CInt(Int(1000 * Rnd()) + 1))
Progres.Value = a
lbltext2.Text = a &
" / " & PROGRES_MAX
If
a < PROGRES_MAX Then
Print(f, x &
", ")
Else
Print(f, x &
".")
End
If
Application.DoEvents()
Next
a
FileClose(f)
frm.Close()
End
Sub
Private
Sub frm_Load(ByVal
sender As Object,
ByVal e As
EventArgs)
frm.Top =
(Screen.PrimaryScreen.Bounds.Height - frm.Height) \ 2
frm.Left =
(Screen.PrimaryScreen.Bounds.Width - frm.Width) \ 2
End
Sub
End Class
Na vytvořeném vlákně bude prováděn kód procedury Metoda, který realizuje následující činnosti:
Testujeme první vícevláknovou
aplikaci
Jste-li s programováním hotovi, můžete aplikaci sestavit (Build è Build Solution). Jestliže aplikaci spustíte, uvidíte, že svoji práce odvádí skvěle (obr. 2).
Obr. 2 – Vícevláknová
aplikace v akci
Jak se ovšem dovíme, že generování náhodných čísel, jako i další kód, jenž je uložen v proceduře Metoda skutečně běží na nově vytvořeném vlákně? Inu, asi takto:
Obr. 3 – Vložení zarážky do programového kódu
|
O správnosti umístění zarážky vás ujistí červený puntík, jenž se
objeví v levém panelu. Kromě červeného puntíku si můžete také všimnout
červený pruh, kterým je zvýrazněn řádek programového kódu, na němž je zarážka
umístěná. |
|
Jestliže na nějaký řádek programového kódu umístíme zarážku, dáme tím
debuggeru Visual Basicu příkaz, aby při přechodu na řádek se zarážkou
zastavil běh aplikace a zpřístupnil dodatečné možnosti pro odlaďování kódu
aplikace. |
Zařazení programové zarážky je výhodné především proto, že jakmile debugger Visual Basicu „skočí“ na řádek se zarážkou, bude běh aplikace pozastaven a my budeme moci prozkoumat vnitřní programové jádro aplikace. Pozastavenou aplikaci lze samozřejmě dále rozběhnout, nebo ji můžete ukončit.
|
Dialogové okno Threads můžete zobrazit také pomocí klávesové
zkratky CTRL+ALT+H. |
Obr. 4 – Dialogové okno Threads
Operace s vlákny
Jakmile je vlákno vytvořeno, můžete s ním provádět několik operací. Rámec potenciálních akcí pro práci s vlákny zastřešuje třída Thread se svými členskými metodami a vlastnostmi. V následující tabulce se nachází přehled některých klíčových metod a vlastností pro práci s vlákny, o nichž si myslím, že by mohly být pro vás užitečné.
Název |
Typ
členu |
Charakteristika |
|
Abort |
|
Metoda |
Metoda se používá pro
zrušení vlákna. Po zavolání metody je ve vybraném vláknu generována výjimka ThreadAbortException,
nacož je spuštěn proces rušení vlákna. |
CurrentThread |
|
Vlastnost |
Jde o sdílenou
(shared) vlastnost, což znamená, že jde o vlastnost samotné třídy Thread
a nikoliv instance této třídy. Použijete-li tuto vlastnost, získáte přístup
k vláknu, jehož programový kód je právě prováděn. |
Name |
|
Vlastnost |
Pokud chcete vlákno
pojmenovat, případně chcete-li jméno vlákna získat, můžete použít tuto
vlastnost. Hodnotou vlastnosti je textový řetězec, jenž představuje
uživatelské jméno pro požadované vlákno. |
Priority |
|
Vlastnost |
Vlastnost určuje
prioritu vlákna. Každé vlákno se může nacházet v několika prioritních
stavech: ·
Highest (Nejvyšší priorita) ·
AboveNormal (Vyšší nežli normální priorita) ·
Normal (Standardní priorita) ·
BelowNormal (Nižší nežli normální priorita) ·
Lowest (Nejnižší priorita) Hodnotou vlastnosti
je jeden z členů enumerace ThreadPriority (AboveNormal, BelowNormal,
Highest, Lowest, Normal). |
ResetAbort |
|
Metoda |
Jde o sdílenou
(shared) metodu, která likviduje požadavek na zrušení (Abort) vlákna. |
Resume |
|
Metoda |
Metodu lze aplikovat
na vlákno, které bylo uvedeno do stavu nečinnosti použitím metody Suspend.
Metoda zabezpečí probuzení vlákna a exekuci jeho programového kódu. |
Sleep |
|
Metoda |
Jde o sdílenou
(shared) a také přetíženou metodu, která se vyskytuje ve dvou exemplářích. Obě
přetížené varianty metody zamezí provádění programového kódu aktivního vlákna
na určitý časový interval (tento je zvyčejně měřen
v milisekundách). |
Start |
|
Metoda |
Zavolání metody
způsobí rozběhnutí programového kódu na daném vlákně. Vláknu, resp. instanci
třídy Thread, je po aktivaci metody Start přiřazen stav ThreadState.Running,
což znamená, že vlákno je aktivní (je realizován kód vlákna). Jestliže bylo
vlákno jednou ukončeno, nelze jej opět aktivovat prostřednictvím opětovného
volání metody Start. |
Suspend |
|
Metoda |
Metoda Suspend
je podobně jako metoda Sleep používaná na „uvedení vlákna do stavu
spánku“. Mezi těmito dvěma metodami ovšem existuje několik podstatných
rozdílů. Především, metoda Sleep je sdílená (shared), což znamená, že
ji možno aplikovat jenom na vlákno, jehož programový kód se právě
uskutečňuje. Metoda Suspend je naproti tomu metodou instance a nikoliv
třídy, z čehož vyplývá, že tuto metodu můžete použít na jakoukoliv
instanci třídy Thread. Druhým odlišným znakem je skutečnost, že
jestliže je použita metoda Sleep, je předem definovaný časový interval
(v milisekundách), v rámci něhož bude vlákno spát. Na druhé straně,
bude-li zavolána metoda Suspend, nelze předem určit, kdy má dojít k
uspání vlákna. Ve skutečnosti dochází k uspání vlákna v okamžiku, kdy je
dosažen tzv. bezpečný bod. Určení existence bezpečného bodu má na starosti
Common Language Runtime (CLR) pomocí služby Garbage Collection. A konečně,
poslední odlišnost spočívá v opětovné aktivaci deaktivovaného vlákna.
Bude-li vlákno uspáno pomocí metody Suspend, lze jej ihned probudit
zavoláním metody Resume. Uspíte-li však vlákno metodou Sleep,
neexistuje žádný způsob, jak jej probudit předtím, nežli vyprší stanovená
doba vlákna pro nečinnost. |
ThreadState |
|
Vlastnost |
Každé vlákno se může
nacházet v určitém počtu stavů. Skutečnosti o stavu vlákna
zprostředkovává právě vlastnost ThreadState. Jde o vlastnost pouze pro
čtení, což znamená, že hodnotu vlastnosti lze přečíst, ovšem není možné ji
jakkoliv modifikovat (toto chování je vzhledem k charakteru vláken
snadno pochopitelné). Hodnotou vlastnosti je jeden z členů enumerace ThreadState. |
Tab. 1 – Charakteristika užitečných vlastností a metod
třídy Thread
|
Právě jste dočetli Téma měsíce.
Jestliže se chcete dozvědět více informací o programování ve Visual
Basicu .NET, neváhejte a navštivte také další sekce rubriky Visual Basic. A
jakáže je dnešní nabídka? |
|
Klasifikace operátorů, aritmetické a porovnávací operátory. |
||
Nepřehlédněte exkluzivní
Speciál pro programátory, který pojednává o jazykové interoperabilitě mezi
jazyky Managed Extensions for C++ a Visual Basic .NET. |