Eine Komponente verwendet mehrere Threads um die gewünschten Aufgaben erledigen zu können.
Wenn nun aus einem dieser Threads ein Event geworfen wird, dann trifft dieser im Calling-Thread ein (also nicht im UI-Thread). Dies ist eigentlich kein Problem... zum Problem wird es erst dann, wenn man in der Eventbehandlung versucht auf die UI-Controls zuzugreifen.....
Zugriffe auf das UI sind nur aus dem UI-Thread erlaubt......
Um nun nicht bei jedem Event überprüfen zu müssen (mittels this.InvokeRequired) habe ich eine Komponenten-Basisklasse geschrieben, welche dies per Default macht.
Die Basisklasse
using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Reflection;
using System.Windows.Forms;
namespace SynchronComponent
{
/// <summary>
/// SynchronComponentBase
///
/// Basisklasse für alle Komponenten mit eigenen Threads
///
/// Author: Programmierhans
/// Source: [url]Komponenten mit mehreren Threads[/url]
/// </summary>
public class SynchronComponentBase : System.ComponentModel.Component
{
#region Fields
//Das Synchronisier-Objekt
private ISynchronizeInvoke _SynchronizingObject=null;
private System.ComponentModel.Container components = null;
#endregion
#region Constructors / IDisposable
//Default - Constructor für den Designer
public SynchronComponentBase(System.ComponentModel.IContainer container)
{
container.Add(this);
InitializeComponent();
}
//Default - Constructor
public SynchronComponentBase()
{
InitializeComponent();
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="pSynchronizingObject">das Objekt auf welches die Events gemarshalled werden sollen</param>
public SynchronComponentBase(ISynchronizeInvoke pSynchronizingObject)
{
InitializeComponent();
this._SynchronizingObject=pSynchronizingObject;
}
public SynchronComponentBase(System.ComponentModel.IContainer container, ISynchronizeInvoke pSynchronizingObject)
{
container.Add(this);
InitializeComponent();
this._SynchronizingObject=pSynchronizingObject;
}
/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#endregion
#region Vom Komponenten-Designer generierter Code
/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
#region Overridden Properties
/// <summary>
/// Dies ist das Objekt in welches die Events gemarshalled werden
/// </summary>
public ISynchronizeInvoke SynchronizingObject
{
get{return this._SynchronizingObject;}
set{this._SynchronizingObject=value;}
}
/// <summary>
/// Trick 77 Abschnitt 5 .... wird hier verwendet um das Synchronizing-Object
/// (also den Parent worauf die Komponente liegt)
/// beim ziehen auf das Form automatisch zu setzen
/// </summary>
public override ISite Site
{
get
{
return base.Site;
}
set
{
base.Site = value;
if (this.Site!=null)
{
System.ComponentModel.Design.IDesignerHost host = (System.ComponentModel.Design.IDesignerHost)value.GetService (typeof (System.ComponentModel.Design.IDesignerHost));
if (host!=null)
{
Control ctrlParent = host.RootComponent as Control;
if (ctrlParent!=null)
{
this._SynchronizingObject=ctrlParent;
}
}
}
}
}
#endregion
#region Protected Methods
/// <summary>
/// Diese Methode dient dazu den Event (sofern SynchronizingObject gesetzt ist) im richtigen
/// Thread zu feuern
/// </summary>
/// <param name="pEventHandlerInstance">Der EventHandler dessen Event geworfen werden soll</param>
/// <param name="sender">der sender-Parameter für den Event</param>
/// <param name="e">Die EventArgs für den Event</param>
protected void InvokeEventOnUIThread(EventHandler pEventHandlerInstance,object sender, EventArgs e)
{
//Wenn der Event-Abonniert ist
if (pEventHandlerInstance!=null)
{
if (this._SynchronizingObject!=null && this._SynchronizingObject.InvokeRequired)
{
//wirf den Event im richtigen (UI)-Thread
this._SynchronizingObject.Invoke(pEventHandlerInstance,new object[]{sender,e});
}
else
{
//Da kein Synchronizer da ist wirf den Event im Calling-Thread (also nicht zwingend
//im UI-Thread)... dies ist das .Net-Default-Verhalten
pEventHandlerInstance(sender,e);
}
}
}
#endregion
}
}
Eine Vererbung dieser Basisklasse kann z.B: so aussehen
using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Threading;
namespace SynchronComponent
{
/// <summary>
/// Zusammenfassung für SampleComponent.
///
/// Beispiel für eine Synchronisierte Komponente mit eigenen Threads
///
/// Author: Programmierhans
/// Source: [url]Komponenten mit mehreren Threads[/url]
/// </summary>
public class SampleComponent : SynchronComponent.SynchronComponentBase
{
#region Fields
/// <summary>
/// Erforderliche Designervariable.
/// </summary>
private System.ComponentModel.Container components = null;
#endregion
#region Constructor / IDisposable
public SampleComponent(System.ComponentModel.IContainer container)
{
///
/// Erforderlich für Windows.Forms Klassenkompositions-Designerunterstützung
///
container.Add(this);
InitializeComponent();
//
// TODO: Fügen Sie den Konstruktorcode nach dem Aufruf von InitializeComponent hinzu
//
}
public SampleComponent()
{
///
/// Erforderlich für Windows.Forms Klassenkompositions-Designerunterstützung
///
InitializeComponent();
//
// TODO: Fügen Sie den Konstruktorcode nach dem Aufruf von InitializeComponent hinzu
//
}
/// <summary>
/// Verwendete Ressourcen bereinigen.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if(components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#endregion
#region Vom Komponenten-Designer generierter Code
/// <summary>
/// Erforderliche Methode für die Designerunterstützung.
/// Der Inhalt der Methode darf nicht mit dem Code-Editor geändert werden.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
#region Events
/// <summary>
/// Dies ist der Event
/// </summary>
public event EventHandler TestEvent;
#endregion
#region Public Methods
/// <summary>
/// Löst den TestEvent aus einem anderen Thread aus
/// aber im UI-Thread !
/// </summary>
public void OnTestInOtherThreadSynchronized()
{
new Thread(new ThreadStart(this.OnTestEventSynchronized)).Start();
}
/// <summary>
/// Löst den TestEvent in einem anderen Thread aus
/// --> dies führt dann in der Event-Behandlung zu einem Problem wenn dort
/// die Zugriffe auf das UI nicht in den UI-Thread gemarshalled werden.
/// </summary>
public void OnTestWithoutSynchronization()
{
new Thread(new ThreadStart(this.OnTestEventUnSynchronized)).Start();
}
#endregion
#region Private Event Invokers - UnSynchronized
/// <summary>
/// Dies ist eine normaler unsynchronisierter Event-Invoker
/// </summary>
private void OnTestEventUnSynchronized()
{
if (this.TestEvent!=null)
{
this.TestEvent(this,EventArgs.Empty);
}
}
#endregion
#region Private Event Invokers - Synchronized
/// <summary>
/// Dies ist der Spezielle Event-Invoker welcher den Event durch die
/// Basisklasse im richtigen Thread (UI) feuern lässt
/// </summary>
private void OnTestEventSynchronized()
{
base.InvokeEventOnUIThread(this.TestEvent,this,EventArgs.Empty);
}
#endregion
}
}
Und noch der Test-Code:
private void sampleComponent1_TestEvent(object sender, System.EventArgs e)
{
System.Diagnostics.Debug.WriteLine(this.InvokeRequired);
}
private void btnCallSynchronizedEvent_Click(object sender, System.EventArgs e)
{
this.sampleComponent1.OnTestInOtherThreadSynchronized();
}
private void btnUnsynchronized_Click(object sender, System.EventArgs e)
{
this.sampleComponent1.OnTestWithoutSynchronization();
}
Wie man sieht wird im Debugger false ausgegeben wenn man den Synchronisierten-Button drückt.... anderenfalls true....
Das Marshalling in den UI-Thread wird ab sofort durch die Komponente erledigt.... ein Marshalling im Event-behandelnden Code ist somit nicht mehr nötig.
Programmierhans