Többszálú környezetben logikusan feltételezhetnénk, hogy az események esetén extra logikát kell beiktatnunk a szálbiztosság érdekében. Azonban az eventek MulticastDelegate 1 alapúak, ami szálbizos, tehát az event szolgáltató oldalán nincs semmi dolgunk, nem kell extra lock és add, remove utasÃtásokkal felruháznunk az osztályunkat.2
class eventSample
{
public event EventHandler TestEvent;
public void Raise()
{
//esemény kiváltása.
//null check minden esetben kell.
TestEvent?.Invoke(this, EventArgs.Empty);
}
}
A fenti egyszerű példából nézzük meg a TestEvent létrehozásához generált kódot:
[CompilerGenerated]
private EventHandler m_TestEvent;
public event EventHandler TestEvent
{
[CompilerGenerated]
add
{
EventHandler eventHandler = this.TestEvent;
while (true)
{
EventHandler eventHandler2 = eventHandler;
EventHandler value2 = (EventHandler)Delegate.Combine(eventHandler2, value);
eventHandler = Interlocked.CompareExchange(ref this.TestEvent, value2, eventHandler2);
if ((object)eventHandler == eventHandler2)
{
break;
}
}
}
[CompilerGenerated]
remove
{
EventHandler eventHandler = this.TestEvent;
while (true)
{
EventHandler eventHandler2 = eventHandler;
EventHandler value2 = (EventHandler)Delegate.Remove(eventHandler2, value);
eventHandler = Interlocked.CompareExchange(ref this.TestEvent, value2, eventHandler2);
if ((object)eventHandler == eventHandler2)
{
break;
}
}
}
}
Mint látható, a fordÃtó CompilerGenerated attribútummal megjelöli az általa generált részeket és ahogy láthatjuk, az Interlocked.CompareExchange 3 metódussal addig próbálkozik a feliratkozók kicserélésével, amÃg az nem sikerül.
Fogadó oldalon viszont van dolgunk a szálbiztossággal, mivel ha az esemény egy másik szálról lett kiváltva, akkor az eseménykezelÅ‘ logika is ugyanazon a szálon fog futni, mint amin meghÃvták. Ez problémát okozhat Windows Forms, WPF és más UI keretrendszerek esetén. Ezekben az esetekben a probléma abból fakad, hogy a UI elemeket a fÅ‘ szál hozza létre, aminek kizárólagos hozzáférése van ezekhez az objektumokhoz. Ha ezekhez másik szálról szeretnénk hozzáférni, akkor egy InvalidOperationException kivétellel jutalmaz minket a rendszer.
Az esemény fogadó oldalán a szálbiztosságot lock vagy egyéb zárolási módszerekkel tudjuk biztosÃtani a kritikus erÅ‘forrásokhoz.
Megjegyzés: .NET Framework 3 vagy korábbi keretrendszerek esetén a szálbiztosságot események esetén nekünk kell biztosÃtanunk, mivel ezek a fordÃtók még nem rendelkeztek ezzel az automatizmussal. A könyv ezen része direkt nem foglalkozik saját implementálással, mivel akkor megragadhatna egy olyan példa, ami nem minden esetben működik. Amennyiben tényleg szükséges egy 15+ éves keretrendszer támogatása, akkor az interneten számos példa található 2005-2006 környékérÅ‘l az adott problémára.4
-
https://docs.microsoft.com/en-us/dotnet/api/system.multicastdelegate↩
-
Abban az esetben nem kell külön
addésremovelogika, ha a célunk a szálbiztosság elérése. Ha extra logika kell az események esetén, akkor szükségünk lesz azaddésremoveblokkokra.↩ -
https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked.compareexchange↩
-
https://blog.stephencleary.com/2009/06/threadsafe-events.html↩