Laden...

Komponenten mit mehreren Threads

Erstellt von Programmierhans vor 18 Jahren Letzter Beitrag vor 17 Jahren 21.116 Views
Information von herbivore vor 15 Jahren

Dies ist ein Thread, auf den aus der FAQ verwiesen wird. Bitte keine weitere Diskussion, sondern nur wichtige Ergänzungen und diese bitte knapp und präzise. Vielen Dank!

Programmierhans Themenstarter:in
4.221 Beiträge seit 2005
vor 18 Jahren
Komponenten mit mehreren Threads

Ein Typisches Threading-Problem.

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

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

M
40 Beiträge seit 2006
vor 17 Jahren

hab mir das beispiel grade angeguckt und ausprobiert .. das problem ist das das gnze nur läuft wenn man noch im designer ist .. sobald man das projekt startet ist der host immmer null... mach ich irgend etwas falsch oder ist das beispiel nur für den designer gedacht ... wenn ja kann man das parent control auch während der laufzeit herausfinden?


        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)      //    <-  Host ist zur laufzeit null 
                    {
                        Control ctrlParent = host.RootComponent as Control;
                        if (ctrlParent!=null)
                        {
                            this._SynchronizingObject=ctrlParent;
                        }
                    }
                }
            }
        }

lg mutzel

4.207 Beiträge seit 2003
vor 17 Jahren

Original von Programmierhans
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)

Muss man ja auch nicht ... einfach in jede UI-Methode einen MethodInvoker rein, der den eigentlichen Code als anonymen Delegate aufruft und gut ist.

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de

Programmierhans Themenstarter:in
4.221 Beiträge seit 2005
vor 17 Jahren

Original von Golo

Muss man ja auch nicht ... einfach in jede UI-Methode einen MethodInvoker rein, der den eigentlichen Code als anonymen Delegate aufruft und gut ist.

Gibts erst ab VS 2005 😉

Früher war ich unentschlossen, heute bin ich mir da nicht mehr so sicher...

M
40 Beiträge seit 2006
vor 17 Jahren

Original von Golo
Muss man ja auch nicht ... einfach in jede UI-Methode einen MethodInvoker rein, der den eigentlichen Code als anonymen Delegate aufruft und gut ist.

dann müsste man ja das control verändern und die methoden würden auch über ein delegate aufgerufen werden auch wenn das garnicht nötig ist

ich will das invoke aber in meiner component machen und nicht erst im control .. so das die methode des controls beim klick auf einen button ohne invoke auskommt und beim zugriff von der multithread component per invoke gestartet wird ..

.. mal ganz davon abgesehen das ein invoke mit einem anonymen delegate garnich geht (glaube ich zumindest) EDIT: ok hab mich geirrt

lg martin

4.207 Beiträge seit 2003
vor 17 Jahren

Dann kannst Du das Control aber auch einfach verwenden, ohne Dir Gedanken drum machen zu müssen, ob da nun ein InvokeRequired hin muss oder nicht, da das Control an sich entweder eben threadsafe ist oder eben nicht. Und das ist IMHO die Verantwortlichkeit dessen, der das Control entwickelt.

Invoke und anonyme Delegates schließen sich nicht gegenseitig aus.

Wissensvermittler und Technologieberater
für .NET, Codequalität und agile Methoden

www.goloroden.de
www.des-eisbaeren-blog.de