C# - Az Interlocked osztály használata szálak közötti erőforrás-megosztáskor

forráskód letöltése
A szálak menedzselése igen fejlett támogatással végezhető a .NET rendszerben. Van lehetőségünk szemafor-objektumokat használni, valamint megoldható, hogy a szálak egy menedzser objektum felügyelete alatt fussanak. Ezeket a lehetőségeket egészíti ki az Interlocked osztály, melynek metódusaival és ezek használatával a cikkünk hasábjain megismerkedhetünk.
Az Interlocked osztály – egyszerűen fogalmazva – a párhuzamosan futó szálak között megosztott változókkal kapcsolatos atomi műveleteket végezheti el. Az osztály biztonságos a szálműveletek végzésekor, tulajdonságai viszont nem örökíthetők.
Az osztály támogatást nyújt azoknak a hibáknak az elkerüléséhez, melyek akkor következnek be, ha egy szál próbálja megváltoztatni egy változó értékét, miközben azon egy másik szál dolgozik. Az osztály metódusainak elvégzése nem generál kivételt semmilyen körülmények között.
Az osztály két jelentősebb metódussal rendelkezik. Ezek deklarációi a következők:
Increment
Osztály: Interlocked
public static int Increment(
ref int location
);
Növeli a megadott változó értékét, majd tárolja a művelet eredményét. Egy változó értékének növelése általában nem atomi művelet, ehhez több lépést kell tenni, melyek a következők:
  • A változó tartalmát egy regiszterbe kell tölteni.
  • A változó tartalmát meg kell változtatni.
  • Tárolni kell az értéket a változó példányban.
Paraméterek
ref int location
A változó, melynek értékét növelni kell.
Visszatérési érték
A növelt érték.
Decrement
Osztály: Interlocked
public static int Decrement(
ref int location
);
Csökkenti a megadott változó értékét, majd tárolja a művelet eredményét.
Paraméterek
ref int location
A változó, melynek értékét csökkenteni kell.
Visszatérési érték
A csökkentett érték.
A példa Util.cs forrásállományában létrehoztunk egy MyClass nevű osztályt, melynek name1 és name2 változójának értékét különböző módokon változtatjuk meg. A name1 változó értékét egy Name1 nevű property segítségével, míg a name2 változó értékét a SetName2 metódus segítségével. Az utóbbi változó értékét a GetName2 metódussal kérdezhetjük le.
Mind a property-ben, mind pedig a metódusokban bekövetkezik egy-egy esemény, melyre majd az alkalmazásban kezelőt deklarálunk, és amelynek segítségével így elérhetjük, hogy a művelet kezdetéről és végéről egy-egy tájékoztató szöveg jelenik meg a ListBox kontrolban. Így tudjuk követni, hogy melyik szál milyen műveletet végzett éppen.
WriteEvent("A szál (" + Thread.CurrentThread.GetHashCode().ToString() + ") elkezdte a name2 lekérdezését.");
...
A művelet elvégzéséhez szükségünk van egy AutoResetEvent objektumra, hogy jelezni lehessen az egyes szálak számára, hogy a konkurens szál végzett valamilyen művelettel, és az erőforrás felszabadult.
public AutoResetEvent are = new AutoResetEvent(false);
A MŰVELET gomb lenyomásakor példányosítjuk a MyClass osztályt.
c = new MyClass();
Egy ciklusban elindítunk 5 szálműveletet, melyeket egy ThreadPool objektummal vezérlünk. A műveletek mindegyike meghívja az Access metódust, melyben véletlenszerűen kérdezik le, vagy állítják be a MyClass változóinak értékét a szálak.
for (threadNum=0;threadNum<=Item-1;threadNum++) 
{
  ThreadPool.QueueUserWorkItem(new WaitCallback(Access),c);
}
Ezt követően az aktuálisan futó szál blokkolásra kerül addig, amíg jelzést nem kap a folytatásra.
are.WaitOne();
Az Access metódus meghívásakor – miután valamilyen művelet elvégzésre került a változókon – hívjuk meg az Interlocked osztály Decrement metódusát, hogy a ciklusváltozó értékét csökkentsük, majd amennyiben az már nulla, akkor az AutoResetEvent objektum Set metódusával adjunk jelzést az aktuális szálnak a folytatásra.
if(Interlocked.Decrement(ref Item) == 0)
{
  are.Set();
}
Ennek a hívásnak köszönhető, hogy ugyan 5 szál indítását tenné lehetővé az Item változó, mégis csak két szál indul el és verseng az erőforrásokért.
A művelet elvégzése után a Form másik nyomógombjának segítségével lekérdezhetjük az osztály változóinak aktuális értékét.