Der folgende Codeauszug soll verdeutlichen wie der Einsatz der Klasse AsyncOperation genutzt werden kann, um Controls im UI-Thread, durch ein Ereignis das in einem anderen Thread ausgelöst wurde, zu aktualisieren ohne auf delegates und die Abfrage der Eigenschaft InvokeRequired zurückzugreifen.
Ausgangsbasis ist eine Klasse 'EventService' die Ereignisse anbietet um über Änderungen an Daten zu informieren. Diese ist durch die Schnittstelle 'IEventService' definiert ist. Eine Instanz der Klasse 'EventService' befindet sich in einem anderen Thread (oder Prozess), als dem UI-Thread und bietet Ereignisse an, die z. B. dazu verwendet werden Controls im UI-Thread zu aktualisieren.
public interface IEventService {
event EventHandler Changed;
}
public class EventService : IEventService {
#region IEventService Members
public event EventHandler Changed;
#endregion
public void ChangeSomething() {
// Ändert irgendetwas und feuert dann das Ereignis Changed.
ThrowChangedEvent( EventArgs.Empty );
}
protected virtual void ThrowChangedEvent( EventArgs e ) {
EventHandler changed = Changed;
if ( changed != null ) {
changed( this, e );
}
}
}
Im o. g. Szenario würde man eine InvalidOperationException mit dem u. g. Text erhalten, wenn man z. B. versuchen würde in der EventHandler-Methode eine Textbox mit Text zu füllen.
Ungültiger threadübergreifender Vorgang: Der Zugriff auf das Steuerelement Form1
erfolgte von einem anderen Thread als dem Thread, für den es erstellt wurde.
Um diese threadübergreifenden Vorgänge zu umgehen, ist in der EventHandler-Methode die Eigenschaft InvokeRequired von Controls zu prüfen,
ob ein direkter oder ein indirekter Zugriff (mittels delegate und Invoke) erfolgen kann/muss.
private void OnEventServiceChanged( object sender, EventArgs e ) {
if ( this.InvokeRequired ) {
this.Invoke( new MethodInvoker( DisplayText ) );
}
else {
DisplayText();
}
}
private void DisplayText() {
textBox1.AppendText( "Es hat sich was getan" );
textBox1.AppendText( Environment.NewLine );
}
Eine andere Möglichkeit, diese threadübergreifenden Vorgänge zu ermöglichen ohne die Eigenschaft InvokeRequired zu prüfen und der Definition/Implementierung von Delegates, die für den Invoke benötigt werden, besteht darin eine Wrapper für die Klasse 'EventService' zu implementieren, der ebenfalls die Schnittstelle 'IEventService' implementiert.
Der Wrapper erhält eine Referenz auf den echten 'EventService' und registriert beim diesem alle angebotenen Ereignisse. Intern erstellt der Wrapper im Konstruktor eine AsyncOperation über den AsyncOperationManager, die er zum Auslösen der eigenen Ereignisse benötigt. Löst nun der echte 'EventService' ein Ereignis aus, wird die EventHandler-Methode des Wrappers aufgerufen. In dieser löst der Wrapper über den Aufruf Post(System.Threading.SendOrPostCallback, object) das entsprechende eigene Ereignis aus.
Dabei wird das Ereignisse des Wrappers in dem Thread ausgelöst, in dem er erstellt wurde. Wenn also der Wrapper im UI-Thread erzeugt wird. z. B innerhalb einer Form, dann werden die Ereignisse des Wrappers im UI-Thread ausgeführt und es kann ein direkter Zugriff auf die Controls erfolgen.
Nachfolgend ist die Implementierung der Wrapper-Klasse für den oben angegebenen 'EventService' dargestellt.
public class WrappedEventService : IEventService {
private IEventService _realEventService;
private AsyncOperation _operation;
public WrappedEventService() {
_operation = AsyncOperationManager.CreateOperation( Guid.NewGuid() );
}
public WrappedEventService( IEventService realEventService ) : this() {
RealEventService = realEventService;
}
public IEventService RealEventService {
get { return _realEventService; }
set {
if ( _realEventService != null ) {
_realEventService.Changed -= OnChanged;
}
_realEventService = value;
if ( _realEventService != null ) {
_realEventService.Changed += OnChanged;
}
}
}
private void OnChanged( object sender, EventArgs e ) {
_operation.Post( ThrowChangedEvent, e );
}
protected virtual void ThrowChangedEvent( object e ) {
EventHandler changed = Changed;
if ( changed != null ) {
changed( this, ( EventArgs ) e );
}
}
#region IEventService Members
public event EventHandler Changed;
#endregion
}
In der angehängten Zip habe ich ein Beispielprogramm (Visual Studio 2008 Solution) hinterlegt, um den Sachverhalt praktisch zu verdeutlichen. Der wesentliche Code ist im Konstruktor der Klasse Form1 enthalten. Die wesentlichen Stellen sind mit Kommentaren versehen.
Events, InvokeRequired, AsyncOperation, UI-Thread, Threadübergreifend