Threadsichere Events in C#
In einem Projekt ergab sich die Problemstellung das eine Resource die in einem Thread geladen wird, andere Programmteile über eine erfolgreiche Aktualisierung informiert. Dabei musste natürlich großes Augenmerk auf die Threadsicherheit gelegt werden. Daher musste zunächst eine Lösung her um Events Threadsicher zu feuern. Nach kurzem Suchen bin ich auf den Artikel im GMBSG Blog aufmerksam geworden. Hier wird eine statische Klasse zum threadsicheren auslösen eines Events gezeigt, die zudem noch versucht das Event im Kontext des jeweiligen Threads der das Event empfangen möchte ablaufen zu lassen:
public static class ThreadSafeInvoker
{
public static void Invoke(Delegate method, object[] args)
{
if (method != null)
{
foreach (Delegate handler in method.GetInvocationList())
{
if (handler.Target is Control)
{
Control target = handler.Target as Control;
if (target.IsHandleCreated)
{
target.BeginInvoke(handler, args);
}
}
else if (handler.Target is ISynchronizeInvoke)
{
ISynchronizeInvoke target = handler.Target as ISynchronizeInvoke;
target.BeginInvoke(handler, args);
}
else
{
handler.DynamicInvoke(args);
}
}
}
}
}
Natürlich sind dabei Anpassungen beim Empfänger notwendig. Entweder man leitet seine Klasse von Control ab oder man erstellt eine eigene Implementierung von ISynchronizeInvoke. Da diese Implementierung aber wohl nicht so einfach von statten geht, gehen viele dazu über einfach von Control abzuleiten auch wenn ihre Klasse nichts mit einem Steuerelement zu tun haben. IDesign bietet hier eine Beispielhafte Implementierung für ISynchronizeInvoke an. Da in meinem Projekt das aktualisieren der Resource ohnehin automatisch im Mainthread statt findet, war keine spezielle Implementierung notwendig.
Weiter ist es aber noch wichtig das An- und Abmelden zu bzw. von einem Event threadsicher zu machen.
private CacheItemChangedEventHandler changedEvent;
readonly object changedEventLock = new object();
public event CacheItemChangedEventHandler Changed
{
add
{
lock (changedEventLock)
{
changedEvent += value;
}
}
remove
{
lock (changedEventLock)
{
changedEvent -= value;
}
}
}
Weiter muss beim Auslösen des Events ebenfalls gelockt werden. Da es aber hierdurch ebenfalls zu Deadlocks kommen kann werden die Eventlistener nur im Lockbereich kopiert und anschließend außerhalb des Locks mit der oben erwähnten statischen Klasse informiert:
private void OnChanged()
{
CacheItemChangedEventHandler copy;
lock (changedEventLock)
{
copy = changedEvent;
}
if (copy != null)
{
foreach (Delegate cur in copy.GetInvocationList())
{
ThreadSafeInvoker.Invoke(cur, new object[] { this, new CacheItemChangedEventArgs(this) });
}
}
}
Weiter gab es bei dem Projekt das Problem das es nicht möglich war festzustellen ob sich ein Listener der über das Event informiert werden will bereits bei der Resource registriert hat oder nicht. Eine Möglichkeit wäre gewesen, jedes Resourcenobjekt in eine Liste zu schmeißen und beim registrieren zum Event vorher zu testen ob für die Resource bereits ein Eintrag in der Liste vorhanden ist und wenn ja muss keine erneute Registrierung erfolgen. Da diese Praktik mir als unschön erschien und sich jede Klasse merken müsste ob sie sich schon registriert hat, wurde die add Methode des Events etwas erweitert:
public event CacheItemChangedEventHandler Changed
{
add
{
lock (changedEventLock)
{
if (changedEvent != null)
foreach (Delegate cur in changedEvent.GetInvocationList())
if (cur.Target == value.Target)
return;
changedEvent += value;
}
}
...
}
So wird ein neuer Listener nur noch hinzugefügt wenn er nicht schon vorhanden ist.
Für Kritik und Anregungen wäre ich euch sehr dankbar.
About this entry
You’re currently reading “Threadsichere Events in C#,” an entry on BeagleOutOfBoundsException
- Published:
- 9.2.09 / 11am
- Category:
- Software Entwicklung
- Tags:
No comments
Jump to comment form | comments rss [?] | trackback uri [?]