A többszálú programozás viszonylag új technika, segítségével lehetőség nyílik a processzor hatékonyabb kihasználására, több processzoros rendszerek programozására. A több szálat használó alkalmazások fejlesztésének lehetősége nem a .NET újítása, de hatékony támogatást ad azok készítéséhez.
A párhuzamos, vagy többszálú programok fogalma megtévesztő lehet. Egy processzor esetén nem fejezik be futásukat hamarabb, és a szálak külön – külön sem futnak rövidebb ideig, viszont több processzor esetén a futási idő radikálisan csökkenhet.
Egy processzoros rendszerekben is előnyt jelenthet a többszálú programok írása, futtatása. Előfordulhat, hogy több program, vagy egy program több szála fut egy időben, s mialatt az egyik szál adatokra vár, vagy éppen egy lassabb perifériával, eszközzel kommunikál, a többi szál használhatja az értékes processzoridőt. Természetesen az egyes szálakat valamilyen algoritmus ütemezi, s azok szeletei egymás után futnak és nem egy időben.
A .NET rendszerben akkor is szálkezelést valósítunk meg, amikor egyszerű programokat írunk. A futó alkalmazásunk is egy programszál, ami egymagában fut, használja a különböző erőforrásokat és verseng a processzoridőért.
Amennyiben tudatosan hozunk létre többszálú programokat gondoskodni kell arról, hogy a szálkezelő elindítsa és vezérelje a szálakat. A .NET a szálkezelőnek átadott szálakat ütemezi, és megpróbálja a számukat is optimalizálni, nagyban segítve ezzel a programozó dolgát. A szálakat természetesen magunk is elindíthatjuk, még a számukat is megadhatjuk, de mindenképpen jobb ezt a rendszerre bízni.
(Az következőekben ismertetett programhoz a Microsoft által kiadott ”David S Platt: Bemutatkozik a Microsoft .NET” című könyvben publikált Párhuzamos Kódokat bemutató fejezetéből vettük az alapötletet, majd ezt fejlesztettük tovább és írtuk át C# nyelvre, mert talán így tudjuk a legegyszerűbben bemutatni a több szálú programokat. Az említett könyv olvasása javasolt annak, aki a .NET rendszer használatát és finomságait teljes mértékben meg akarja ismerni!)
A példaprogram egy listView komponens segítségével bemutatja, hogyan futnak egymás mellett a szálak, a szálakat egy ImageList-ben tárolt képsorozat elemeivel szimbolizálja. A programban egy időben több szál is fut, de a processzor egy időben csak eggyel foglalkozik, viszont cserélgeti a futó szálakat.
A Piros ikonok az aktív szakaszt mutatják, a sárgák az aktív szakasz utáni alvó periódust, a kék ikonok pedig a még feldolgozásra váró, és a már nem futó szálakat szimbolizálják. A programban szerepel egy célfüggvény, mely a szálakhoz rendelt eseményt hajtja végre. Az egyszerűség kedvéért a program célfüggvénye csak a szál ikonját és feliratát cserélgeti, ezzel jelezve, hogy egy valós, párhuzamos program ezen részében adatfeldolgozás, vagy egyéb fontos esemény történik.
Az egyszerre futó szálak számát nem a programozó határozza meg, hanem a .NET keretrendszer az erőforrások függvényében.
Induláskor közöljük a programmal a futtatni kívánt szálak számát, majd átadjuk a célfüggvényt a System.Threading.ThreadPool.QueueUserWorkItem() metódusnak, mely feldolgozza ezeket.
A célfüggvény mellett adhatunk a metódusnak egy tetszőleges objektumot, melyben paramétereket közölünk vele, ami átadja ezeket a célfüggvénynek.
A példában az egy mezőt tartalmazó Parameterek osztályt adunk át a paraméter listában, melyben az adott elem sorszámát közöljük a célfüggvénnyel.
class Parameterek
{
public int i = 0;
public String s = ”Szál”;
}
A program indulása után a Form- ra helyezett nyomógomb indítja el a szálkezelést.
A nyomógomb eseménykezelőjében, a FOR ciklusban példányosítjuk a Parameterek osztályt (minden szálhoz egyet):
Parameterek P = new Parameterek();
Hozzáadjuk a soron következő elemet a listView komponenshez, beállítjuk az elem ikonját és feliratát:
listView1.Items.Add(Convert.ToString(i)+"Szál",2);
Értékül adjuk a ciklus változóját az osztály i változójának, ezzel átadva az adott szál sorszámát a szálkezelőnek, s közvetett úton a célfüggvénynek:
P.i=i;
Végül átadjuk a példányt a célfüggvénnyel együtt a szálkezelőnek, mely gondoskodik a szál sorsáról. Ütemezi, futtatja, majd leállítja azt.
System.Threading.WaitCallback CF = new System.Threading.WaitCallback(CelF);
System.Threading.ThreadPool.QueueUserWorkItem(CF ,P);
A célfüggvény a következő dolgokat végzi el, szimulálva egy valós program-szál működését:
Beállítja a szál ikonját a piros, „futó” ikonra, (Az ikonokat egy imageList- ből veszi), azután átírja az ikon feliratát:
listView1.Items[atadot.i].ImageIndex=0;
listView1.Items[atadot.i].Text="Futó szál";
A System.Thread.Threading.Sleep(1000) metódussal a szálkezelő várakoztatja a szálat, majd átállítja a szálhoz tartozó ikont a sárga, „alvó” ikonra, a feliratát pedig „Alvó szál” - ra:
listView1.Items[atadot.i].ImageIndex=1;
listView1.Items[atadot.i].Text="Alvó szál";
System.Threading.Thread.Sleep(1000);
A szál ismét várakozik, majd az ikonja az eredetire vált:
listView1.Items[atadot.i].ImageIndex=2;
listView1.Items[atadot.i].Text="Vége”);
System.Threading.Thread.Sleep(1000);
Mivel a szálak létrehozását ciklusba szerveztük, a program annyi szálat hoz létre, mint a ciklus lépésszáma, viszont azt, hogy egyszerre hány szál fut, a szálkezelő dönti el.
A listView ablakában látszik, hogy egyszerre akár három, vagy négy szál is futhat, és mellettük néhány szál éppen alszik, de lassabb gépeken ezek a számok növekedhetnek.
Sajnos a többszálú programozás közel sem ilyen egyszerű. Bonyolultabb alkalmazások esetén egy probléma mindenképpen felvetődik. A futó szálak nem használják egy időben ugyanazt az erőforrást, vagyis biztonságosan kell futniuk egymás mellett. A jól megírt többszálú programok optimálisan használják fel az osztott erőforrásokat, de csak akkor használjuk fel a lehetőségeiket, ha tisztában vagyunk a használatukban rejlő előnyökkel, és a lehetséges hibákkal is. A rosszul megírt párhuzamos működésű programokkal éppen az ellenkezőjét érjük el annak, amit szeretnénk. Növeljük a lépésszámot, a memória foglaltságát és a programok hatékonyságát.