.NET Works: Events threadsicher aufrufen in .NET
Threadsafety bei Events in .NET: Es geht auch einfach
Ab und an schreibt man eine Windows-Forms-Anwendung. Man entwickelt seine eigenen Geschäfts-Klassen und möchte diese natürlich so interaktiv wie möglich mit der Bedienungsoberfläche koppeln. Events sind da ein willkommenes Mittel, um dem UI mitzuteilen, dass sich signifikantes im Business-Layer geändert hat.
Nun ist dieser Vorgang nichts weltbewegendes - bis die Ansprüche steigen und die Geschäftsklassen in einem separaten Background-Thread ihre Arbeit erledigen. Ehe man sich versieht, sitzt man schon vor dem Problem, die von den Geschäftsklassen erzeugten Events threadsicher im UI-Code zu verarbeiten.
Microsoft hat diesem oft vorkommenden Szenario in der Anwendungsentwicklung einen eigenen Artikel gewidmet. Das Verarbeiten des Events von einem anderen Thread wird hier klassisch über einen Selbstaufruf gelöst:
namespace ICF
{
public class Form1 : Form
{
delegate void SetTextCallback(string text);
...
private void SetText(string text)
{
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
else
{
this.textBox1.Text = text;
}
}
}
}
Der Ansatz ist durchaus praktikabel, aber aufwendig. Wenn man auf diese Art und Weise das gesamte UI mit threadsicheren Methodenaufrufen ausstatten muss, kann das schnell in Arbeit ausarten.
Die Alternative: Thread-Safety beim Event-Aufruf
Doch die oben gezeigte Möglichkeit ist nicht die einzige. Um den Aufwand bei der Verarbeitung zu minimieren, könnte man ja schon beim Aufrufen des Events Maßnahmen einleiten, die eine threadsichere Verarbeitung gewährleisten.
Dieser Ansatz ist relativ einfach lösbar, denn schließlich ist ein Event nichts anderes als ein Delegate; der wiederum bietet die Möglichkeit der Verarbeitung der gesamten Aufrufsliste mit GetInvocationList(). Jetzt muss nur noch überprüft werden, ob das Aufrufziel ein Windows-Forms-Control ist oder die ISynchronizeInvoke-Schnittstelle unterstützt.
Im Ergebnis hat man dann eine kleine Hilfsklasse, die garantiert, dass der Event threadsicher am Ziel ankommt:
using System;
using System;
using System.ComponentModel;
using System.Windows.Forms;
namespace ICF
{
public static class ThreadSafe
{
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);
}
}
}
}
}
}
Jetzt muss man nur noch diese kleine Hilfsmethode beim "feuern" des Events in der Business-Klasse verwenden - und schon sind die Events der Klasse threadsicher verarbeitbar.
namespace ICF
{
public class BusinessClass
{
public event EventHandler<EventArgs> Executed;
...
private void Execute()
{
...
if (this.Executed != null)
{
ThreadSafe.Invoke(this.Executed, new object[] { this, EventArgs.Empty });
}
}
...
}
}
