C# - A Thread osztály bemutatása

forráskód letöltése
Ebben a cikkben megismerhetjük a Thread osztályt, annak property-jeit és metódusait. Egy példán keresztül bemutatjuk, hogyan foglalhatunk le névvel rendelkező memóriaterületet egy több szálat futtató process memóriaterületén belül, melyet a szálak megosztva elérhetnek.
A Thread osztály tagjai
Az alkalmazások szálait reprezentáló Thread osztály lehetőségeit számba véve látható, hogy az osztály számtalan property-vel rendelkezik, amelyek lekérdezésével jól körülhatárolható képet kaphatunk az adott szálról. A függvények könnyen használhatók, legtöbbjük paraméter nélkül hívható meg egy adott szálra, mely nagyon megkönnyíti a szálakkal való munkát.
A cikk első felében bemutatjuk az osztály tagjait, majd a mellékelt alkalmazásban megvizsgáljuk felhasználásukat.
Az osztály property-jei a következők:
ApartmentState
Osztály: Thread
public ApartmentState ApartmentState {get; set;}
Megadja az adott szál ’apartment-állapotát’.
Az ’apartment’ egy logikai konténer a process területén belül. Az adott apartment-en belül minden objektum fogadhat hívást az apartment minden szálától. A property beállításával adhatjuk meg a CLR-nek, hogy ún. menedzselt szálak esetén milyen apartment kerüljön létrehozásra. Három lehetséges értéke van:
Értékek Jelentés
STA (Singlethreaded Apartment) A szál egy szálat engedélyező apartment-et hoz létre és inicializál.
MTA (Multithreaded Apartment) A szál több szálat engedélyező apartment-et hoz létre és inicializál.
Unknown Az ApartmentState property nincs beállítva.
A property értéke csak akkor állítható be, amikor a szál Running vagy Unstarted állapotban van; egyszer állatható be futás során.
CurrentContext
Osztály: Thread
public static Context CurrentContext {get;}
Megadja az adott Context objektumot, amelyben a szál fut.
CurrentCulture
Osztály: Thread
public CultureInfo CurrentCulture {get; set;}
Megadja az adott szál kulturális beállításait.
CurrentPrincipal
Osztály: Thread
public static IPrincipal CurrentPrincipal {get; set;}
Megadja, hogy milyen szabály-alapú biztonsági beállítások vannak érvényben az adott szálra, vagyis közvetve azt, hogy melyik felhasználó jelentkezett be a számítógépen.
Ennek a property-nek az értelmezéséhez pár szót ejtenünk kell a .NET biztonsági lehetőségeiről. A közös nyelvi futtató rendszer és a .NET Framework számtalan osztályt és szolgáltatást biztosít annak érdekében, hogy a fejlesztők biztonságos kódot írjanak. Az osztályok és szolgáltatások lehetővé teszik, hogy az ezekkel írt kódoknak a rendszeradminisztrátorok testre szabhassák hozzáférésüket védett erőforrásokhoz. A kódhozzáférés szabályozására több módszer is rendelkezésre áll, ezek közül az ún. kulcs és a szerep-alapú biztonsági rendszerrel van összefüggésben a property-ben megadandó érték.
A szerep-alapú biztonsági rendszer esetén az ún. elöljáró (’principal’) reprezentálja az azonosítóját és szerepét az adott felhasználónak, valamint a szerephez kapcsolt viselkedést. Az említett biztonsági rendszer háromféle elöljárót támogat:
Elöljáró Jelentés
Általános elöljáró Azok a felhasználók és szerepek, melyek a Windows NT és Windows 2000 felhasználóktól és szerepektől függetlenül léteznek.
Windows elöljáró Reprezentálja a Windows rendszer felhasználóit és azok szerepeit.
Alkalmazás-specifikus elöljáró Alkalmazásban megadható elöljáró, mely az alapvető szerepek kiterjesztése.
A menedzselt kód a Principal osztályon keresztül érheti el az elöljáró azonosítóját és a kapcsolódó szerepet. Az azonosítót egy Identity típusú hivatkozáson keresztül érheti el.
A hálózatok többségében egy felhasználói azonosító reprezentálja a felhasználókat és programokat, míg a csoport azonosító a felhasználói csoportokat és ezek jogait. Az elöljárók a .NET rendszerben egységbe zárják az azonosítókat és szerepeket.
A mellékelt alkalmazásban egy, az IPrincipal interfészt implementáló WindowsPrincipal nevű osztály segítségével érjük el az adott elöljárót.
CurrentThread
Osztály: Thread
public static Thread CurrentThread {get;}
Visszaad egy hivatkozást az aktuálisan futó szálra.
IsAlive
Osztály: Thread
public bool IsAlive {get;}
Visszaadja, hogy az adott szál él-e. True érték esetén az adott szál elindított állapotban van, False esetén nem.
IsBackground
Osztály: Thread
public bool IsBackground {get; set;}
Megadható, hogy az adott szál a háttérben fusson-e, vagy nem. True értéket adva a property-nek a szál háttérben futó szál lesz.
Name
Osztály: Thread
public string Name {get; set;}
Beállítható egy tetszőleges név a szálhoz.
Priority
Osztály: Thread
public ThreadPriority Priority {get; set;}
Prioritás rendelhető az adott szálhoz. Értékének beállításához egy ThreadPriority enumerátor értékeit használhatjuk fel. A szálak prioritását egész számok reprezentálják, melyek meghatározzák a szálak ütemezését abban az esetben, amikor azonos erőforráson végeznek műveletet egy időben. A szálak prioritását az operációs rendszer dinamikusan is képes állítani attól függően, hogy mely munkafolyamat aktív. Értékei a következők:
Értékei Meghatározás
AboveNormal Magasabb, mint a Normál prioritás.
BelowNormal Alacsonyabb, mint a Normál prioritás.
Highest Legmagasabb prioritás.
Lowest Legalacsonyabb prioritás.
Normal Normál prioritás.
Alapértelmezett értéke a Normal.
ThreadState
Osztály: Thread
public ThreadState ThreadState {get;}
Megadja, hogy az adott szál a létrehozásától a megszüntetéséig terjedő időintervallumban éppen milyen futási állapotban van. A szálak létrehozásukkor az Unstarted állapotban vannak. Néhány lehetséges értéke a következő:
Érték Meghatározás
Running A szál fut.
Unstarted A szál elindult
Suspended A szálra az alkalmazásban meghívódott a Suspend metódus.
SuspendRequested Folyamatban van a felfüggesztés.
WaitSleepJoin A szálra az alkalmazásban meghívódott a Wait, Join vagy Sleep metódus.
Background A szál a háttérben fut.
AbortRequested Folyamatban van a megszüntetés.
Stopped A szál leállított állapotban van.
A property-k után lássuk, milyen metódusai vannak az osztálynak:
Abort
Osztály: Thread
public void Abort(
);
Előidéz egy speciális, ThreadAbortException nevű kivételt, mely nem elkapható. A try – finally blokk valamennyi finally elemében megvalósított kód lefut, mielőtt a szál abortál, mivel ez nem jár a szál azonnali megszűnésével. A teljes megszüntetéshez meg kell hívni a szálra a Join metódust.
Abban az esetben, ha a szálra meghívjuk az Abort metódust, de az nincs elindítva, akkor elinduláskor fog abortálni.
AllocateDataSlot
Osztály: Thread
public static LocalDataStoreSlot AllocateDataSlot(
);
Segítségével lefoglalhatunk a process memóriaterületén belül egy területet a szálaknak. A lefoglalt területre névvel nem hivatkozhatunk.
Visszatérési érték
A lefoglalt területet egy LocalDataStoreSlote típusú objektum reprezentálja, mintegy egységként kezelve azt. A memóriaterület azon része, melyet a szálak szál-specifikus adatok tárolására használhatnak fel. A CLR egy multi-slot memóriatömböt hoz létre minden egyes process számára, melyet elindít. A szálak metódusaikkal lefoglalnak területet a tömbben, adatokat tesznek bele, illetve olvasnak ki, majd felszabadítják azt.
AllocateNamedDataSlot
Osztály: Thread
public static LocalDataStoreSlot AllocateNamedDataSlot(
string name
);
Segítségével lefoglalhatunk a process memóriaterületén belül egy területet a szálaknak. A lefoglalt területre névvel hivatkozhatunk.
Paraméterek
string name
Az adatterület neve, mellyel rá hivatkozhatunk.
Visszatérési érték
Azonos az AllocateDataSlot metódusnál említettel.
FreeNamedDataSlot
Osztály: Thread
public static void FreeNamedDataSlot(
string name
);
Segítségével fel tudjuk szabadítani a névvel rendelkező memóriaterületeket a process memóriaterületén belül. A lefoglalt területre névvel hivatkozhatunk.
Paraméterek
string name
Az adatterület neve, mellyel rá hivatkozhatunk.
GetData
Osztály: Thread
public static object GetData(
LocalDataStoreSlot slot
);
A szálak által a tárolóterületen elhelyezett adatok visszanyerését teszi lehetővé.
Paraméterek
LocalDataStoreSlot slot
A lefoglalt memóriaterület objektuma.
Visszatérési érték
A kinyert adatot reprezentáló objektum.
GetDomain
Osztály: Thread
public static AppDomain GetDomain(
);
Megadja, hogy az adott szál milyen alkalmazás futási környezetébe tartozik.
Visszatérési érték
Egy AppDomain típusú objektum, mely információval szolgál a szálat futtató alkalmazásról.
GetDomainID
Osztály: Thread
public static int GetDomainID(
);
Megadja, hogy mi az adott szál domain azonosítója.
Visszatérési érték
Egész szám, mely a domain-t azonosítja.
GetNamedDataSlot
Osztály: Thread
public static LocalDataStoreSlot GetNamedDataSlot(
string name
);
Visszaad egy hivatkozást a paraméterben megadott nevű memóriaterületre, melyet lefoglaltunk.
Paraméterek
string name
Az adatterület neve, mellyel rá hivatkozhatunk.
Visszatérési érték
A lefoglalt memóriaterület objektuma.
Interrupt
Osztály: Thread
public void Interrupt(
);
Megszakít egy WaitSleepJoin állapotban levő szálat.
Join
Osztály: Thread
public void Join(
);
A hívó szál egy másik szálra várhat ezzel a metódussal. Addig vár, amíg az adott szál megszűnik. A metódus másik két implementációjában egy időtartamot azonosító egész számot, vagy egy időbélyeget adhatunk meg, amelyek meghatározzák a szál várakozási idejét.
ResetAbort
Osztály: Thread
public static void ResetAbort(
);
Törli az adott szálra kiadott Abort kérést. A kódnak ControlThread joggal kell rendelkeznie. Ez egy SecurityPermission enumerátor értékének megválasztásával történik.
Resume
Osztály: Thread
public void Resume(
);
A felfüggesztett szálat újra elindítja (folytatás).
SetData
Osztály: Thread
public static void SetData(
LocalDataStoreSlot slot,
object data
);
Adat helyezhető a memóriaterületre.
Paraméterek
LocalDataStoreSlot slot
A lefoglalt memóriaterület objektuma.
object data
A tárolandó adat objektuma.
Sleep
Osztály: Thread
public static void Sleep(
int millisecondsTimeout
);
Adott időre a szál felfüggeszthető.
Paraméterek
int millisecondsTimeout
Az időintervallum.
Start
Osztály: Thread
public void Start(
);
Elindítja az adott szálat, vagyis a létrehozáskor a konstruktorban megadott függvényt futtatja.
Suspend
Osztály: Thread
public void Suspend(
);
Felfüggeszti az adott szálat. A felfüggesztett szálra nincs hatással.
Gyakorlati felhasználás
A mellékelt programban a Form-on található TabControl füleire kattintva érhető el a két funkció-csoport. A ’Műveletek’ fülre kattintva három gomb látható, melyekkel elindítható (Indítás), felfüggeszthető (Szünet), valamint megszüntethető (Vége) az alkalmazásban használt két szál.
A program indításakor egy DataTable tárolót feltöltünk adatokkal. A ’th’ szál, melynek neve ’Adatszál’, folyamatosan rekordokat szúr be ebbe a DataTable tárolóba. A szálak létrehozása a hagyományos módon történik:
private void tButton_Click(object sender, System.EventArgs e)
{
  th = new Thread(new ThreadStart(this.ThreadFunction));
  th1 = new Thread(new ThreadStart(this.ThreadFunction1));
  th.IsBackground = true;
  th1.IsBackground = false;
  th.Name = "Adatszál";
  th1.Name = "Cimkeszál";
  th.Start();
  th1.Start();
A következő lépésben egy FillList metódus feltölti az ’Adatszál’ adataival a TabControl ’Adatszál tulajdonságai’ fül alatt megtalálható ListBox kontrolt.
  FillList();
A gombok alatti listában pedig elhelyezzük a szálak állapotinformációit, melyet minden gombnyomásra frissítünk, tükrözve a helyes állapotot.
  GetThreadStates(th,th1);
Az ’Adatszál’ elhelyezi a frissített adathalmazt a LocalDataStoreSlot objektumba:
  lSlot = Thread.AllocateNamedDataSlot("data");
  DataTable dtHelp = new DataTable();
  dtHelp = dt.Copy();
  Thread.SetData(lSlot,dtHelp);
  ...
}
A szálak indításakor megadott függvények neve ThreadFunction és ThreadFunction1, az elvégzett feladatokat a következő metódusokban valósítottuk meg: Adatszál esetén UpdateData, Cimkeszál esetén RowCount a neve a metódusnak.
A Szünet gomb megnyomásakor a th szálra meghívunk egy Suspend metódust, majd indításához a Resume eljárást. A th1 szálat nem blokkoltuk, azonban állapotára hatással van a másik szál állapota, mivel azonos tárolót használnak.
private void pauseButton_Click(object sender, System.EventArgs e)
{
  if (threadState == 1)
  {
    dieButton.Enabled = false;
    pauseButton.Text = "Folytatás";
    th.Suspend();
    threadState = 0;
    GetThreadStates(th,th1);
  }
  else
  {
    dieButton.Enabled = true;
    pauseButton.Text = "Szünet";
    th.Resume();
    threadState = 1;
    GetThreadStates(th,th1);
  }
}
A Vége gombbal mindkét szálat megszüntetjük az Abort, majd a Join metódusok meghívásával. A művelet végén felszabadítjuk a memóriaterületet is. A szálak aktuális állapota a ListBox kontrolban, a teljes adathalmaz pedig a DataGrid kontrolban látható.
private void dieButton_Click(object sender, System.EventArgs e)
{
  if (th != null && th1 != null)
  {
    dt.Clear();
    dt = (DataTable)Thread.GetData(lSlot);
    grid.DataSource = dt.DefaultView;
    th.Abort();
    th1.Abort();
    th.Join();
    th1.Join();
    GetThreadStates(th,th1);  
    th1 = null;
    th = null;
  }
  Thread.FreeNamedDataSlot("data");
  startButton.Enabled = true;
  pauseButton.Enabled = dieButton.Enabled = false;
}