A megvalósítandó feladat az lesz, hogy egy Label kontrolon fusson egy számláló, melynek aktuális értékét egy külön szálon növeljük. Amíg e számláló fut, programunk addig is képes további műveletek elvégzésére, hiszen Label Text property-jét egy külön szál kezeli, így a felhasználó nyugodtan dolgozhat tovább alkalmazásunkban.
Új szál létrehozásához szükségünk lesz a Thread osztály egy példányára, így készítsünk ehhez egy változót.
A számláló indítása előtt ellenőriznünk kell, hogy nem lett-e már létrehozva a szál.
private void button1_Click(object sender, System.EventArgs e)
{
if (th != null)
{
Ha a szál már létezik, akkor annak futását megszakítjuk.
th.Interrupt();
th = null;
}
Most már biztonságosan létrehozhatunk egy új szálat, melyhez kell egy új Thread osztály. Ennek konstruktora egy ThreadStart osztály példányát várja, így ebből is létrehozunk egyet. A ThreadStart konstruktora egy olyan függvényt vár, mely az új szál fő funkciója lesz, vagyis ebben a függvényben van megadva a külön szálon futó kód.
th = new Thread(new ThreadStart(ThreadFunction));
A szál létrehozása után az IsBackground property-n keresztül rendelkezhetünk arról, hogy a szál a háttérben fusson-e.
A Start függvényét meghívva pedig elindíthatjuk a szálat.
A szál létrehozásánál adtuk meg a ThreadFunction függvényünket, mely a Thread osztály Start függvényének hívásakor induló új szál kódját tartalmazza. Az új szál addig fut, amíg e függvény be nem fejezi a futását, vagy amíg az új szálat kívülről meg nem szakítják.
public void ThreadFunction()
{
try
{
Abban az esetben, ha egy szálon belül egy vizuális kontrolt szeretnénk elérni, mint jelen esetben is, akkor ezt csak úgy tehetjük meg, hogy MethodInvoker-t hívjuk segítségül, ennek konstruktorában megadjuk annak a függvénynek a nevét, mely elvégzi a Label szövegének frissítését.
MethodInvoker mi = new MethodInvoker(UpdateLabel);
Mivel azt szeretnénk, hogy ez a szál addig fusson, ameddig a program, vagy amíg meg nem szakítják, így egy végtelen ciklust kezdeményezünk.
A ciklusmagon belül fogjuk aktivizálni az UpdateLabel függvényünket a BeginInvoke függvény hívással.
BeginInvoke(mi);
Thread.Sleep(50);
}
}
catch
{
}
}
A szál kódját célszerű mindig try - catch blokkba helyezni, mivel a szál külső megszakításakor (Interrupt függvény) mindig egy ThreadInterruptedException hiba keletkezik. Ha szükségünk van rá, akkor erre akár fel is készíthetjük a szál kódját az alábbi módon:
catch (ThreadInterruptedException)
{
...
}
Így a külön szálon futó kód is értesítést kaphat arról, hogy kívülről megszakították a futását és ilyenkor még elvégezheti a szükséges műveleteket mielőtt futása végleg leállna.
Az UpdateLabel függvénynek most már csak annyi a dolga, hogy növelje a számláló értékét eggyel.
private void UpdateLabel()
{
label1.Text = (Convert.ToInt32(label1.Text) + 1).ToString();
}
Ha a program futása közben szeretnénk leállítani az újonnan indított szálat, akkor ehhez az Interrupt függvényét kell meghívnunk. Előtte persze célszerű ellenőriznünk, hogy a szál objektuma valóban létrejött-e, különben leállítani sem tudjuk, de egy hibaüzenetet biztos kapunk, ha mégis megpróbáljuk.
private void button2_Click(object sender, System.EventArgs e)
{
if (th != null)
{
th.Interrupt();
th = null;
}
}
Fontos, hogy programunk bezárásakor, ha még van futó szál, melyet mi indítottunk az alkalmazásunkban, akkor azt leállítsuk. Ehhez a Form Dispose függvénye használható fel, mely akkor kerül meghívásra, mikor a programunk bezárása történik. Itt nincs más teendőnk, mint szintén az Interrupt segítségével leállítani a szálat, ha az még futna.
protected override void Dispose( bool disposing )
{
if (th != null)
{
th.Interrupt();
th = null;
}