Laden...

Mehrere MessageLoops und Forms in anderen Threads

Erstellt von Programmierhans vor 18 Jahren Letzter Beitrag vor 18 Jahren 2.846 Views
Programmierhans Themenstarter:in
4.221 Beiträge seit 2005
vor 18 Jahren
Mehrere MessageLoops und Forms in anderen Threads

Manchmal wäre es wünschenswert, wenn man ein Form in einem anderen Thread laufen lassen könnte... wieso könnte ?

Das wichtige dabei ist:

  • Jeder Thread welcher mit Controls / Forms arbeitet braucht seinen eingenen MessageLoop.
  • sämtliche Zugriffe von Form zu Form müssen Gemarshalled werden (wird in diesem Beispiel NICHT erklärt, da hier KEINE Kommunikation von Form zu Form stattfindet).

Das Background-Objekt dient als Startpunkt für die verschiedenen Threads.



using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace ThreadCompleteTest
{
	public class BackgroundObject
	{

		//Platzhalter für das Synchronisierungs-Objekt
		private ISynchronizeInvoke _SynchronizingObject=null;
		private ThreadStart _CallBackMethod=null;

		public BackgroundObject(ISynchronizeInvoke pCaller, ThreadStart pCallBackMethod)
		{
			this._SynchronizingObject=pCaller;
			this._CallBackMethod=pCallBackMethod;
		}

		public void StartThread()
		{
			//hier kommt der Code hin welcher im Thread ablaufen soll.
			//als Beispiel ein Form welches in einem anderen Thread läuft
			//Auf dieses Form darf natürlich nicht ohne Marshalling zugegriffen werden !!!
			//daher wird es auch nur in dieser Funktion lokal deklariert
			FormInAnderemThread frm=new FormInAnderemThread();
			Application.Run(frm);


			//das Form wurde geschlossen (und der zusätzliche MessageLoop auch)

			//und hier wird der Callback im Thread des Callers ausgeführt.
			if (this._SynchronizingObject!=null && this._CallBackMethod!=null)
			{
				this._SynchronizingObject.Invoke(this._CallBackMethod,new object[]{});
			}
		}
	}
}


Das folgende Form läuft in einem anderen Thread mit eigenem MessageLoop




using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Threading;

namespace ThreadCompleteTest
{
	/// <summary>
	/// Summary description for FormInAnderemThread.
	/// </summary>
	public class FormInAnderemThread : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Label lblThreadID;
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public FormInAnderemThread()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			//
			// TODO: Add any constructor code after InitializeComponent call
			//
			this.lblThreadID.Text=Thread.CurrentThread.GetHashCode().ToString();
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if(components != null)
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.lblThreadID = new System.Windows.Forms.Label();
			this.SuspendLayout();
			// 
			// lblThreadID
			// 
			this.lblThreadID.Location = new System.Drawing.Point(8, 8);
			this.lblThreadID.Name = "lblThreadID";
			this.lblThreadID.Size = new System.Drawing.Size(200, 48);
			this.lblThreadID.TabIndex = 0;
			// 
			// FormInAnderemThread
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(216, 61);
			this.Controls.Add(this.lblThreadID);
			this.Name = "FormInAnderemThread";
			this.Text = "FormInAnderemThread";
			this.ResumeLayout(false);

		}
		#endregion

	}
}


Und nun noch den Startcode auf dem Hauptform.




using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Threading;

namespace ThreadCompleteTest
{
	/// <summary>
	/// Summary description for Form1.
	/// </summary>
	public class Form1 : System.Windows.Forms.Form
	{
		private System.Windows.Forms.Button button1;
		private int _StartedForms=0;
		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public Form1()
		{
			//
			// Required for Windows Form Designer support
			//
			InitializeComponent();

			//
			// TODO: Add any constructor code after InitializeComponent call
			//
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		#region Windows Form Designer generated code
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
			this.button1 = new System.Windows.Forms.Button();
			this.SuspendLayout();
			// 
			// button1
			// 
			this.button1.Location = new System.Drawing.Point(104, 40);
			this.button1.Name = "button1";
			this.button1.TabIndex = 0;
			this.button1.Text = "button1";
			this.button1.Click += new System.EventHandler(this.button1_Click);
			// 
			// Form1
			// 
			this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
			this.ClientSize = new System.Drawing.Size(292, 273);
			this.Controls.Add(this.button1);
			this.Name = "Form1";
			this.Text = "Form1";
			this.ResumeLayout(false);

		}
		#endregion

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main() 
		{
			Application.Run(new Form1());
		}

		private void button1_Click(object sender, System.EventArgs e)
		{
			//erstelle ein neues BackgroundObject welches in einem andern Thread laufen soll
			//ThreadComplete soll aufgerufen werden wenn der Thread abläuft.
			BackgroundObject bo=new BackgroundObject(this,new ThreadStart(this.ThreadComplete));
			//bo.StartThread ist die Methode welche im andern Thread läuft.
			Thread t=new Thread(new ThreadStart(bo.StartThread));
			//starte den Thread
			t.Start();
			//überwache die Anzahl der Threads (hier sogar Forms mit eigenem MessageLoop)
			this._StartedForms+=1;
		}

		private void ThreadComplete()
		{
			//ein Thread wird sich gleich anschliessend beenden
			string strMessage=this.InvokeRequired?"Thread wird beendet... diese Meldung läuft NICHT im UI-Thread":"Thread wird beendet... diese Meldung läuft im UI-Thread";
			System.Diagnostics.Debug.WriteLine(strMessage);
			this._StartedForms-=1;
		}

		protected override void OnClosing(CancelEventArgs e)
		{
			//verhindere dass dieses Form geschlossen wird bevor alle anderen Forms geschlossen sind
			e.Cancel=this._StartedForms>0;
			base.OnClosing (e);
		}

	}
}


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

I
1.739 Beiträge seit 2005
vor 18 Jahren

Hübsch.
Frage: Was passiert wenn ein Thread sich aufhängt?


        protected override void OnClosing(CancelEventArgs e)
        {
            //verhindere dass dieses Form geschlossen wird bevor alle anderen Forms geschlossen sind
            e.Cancel=this._StartedForms>0;
            base.OnClosing (e);
        }


Warum nicht alle prüfen und schliessen?

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

Original von ikaros

Frage: Was passiert wenn ein Thread sich aufhängt?

Warum nicht alle prüfen und schliessen?

Im Thread läuft hier ja nur ein einziges Form (und ein Form sollte sich nicht aufhängen)... ansonsten aber eine berechtigte Frage.

Wenn ich die einzelnen Threads prüfen wollte müsste ich ein Array der BackGround-Objekte halten (was ich hier der einfachheit halber nicht gemacht habe)...

Ich sehe den Einsatz dieses Samples eher um einen einzelnen Prozess zu visualisieren welcher z.B: Grafik-Intensiv ist... so wird der "normale" UI-Thread nicht belastet (anderenfalls müssten ja die berechneten Daten in den UI-Thread gemarshalled werden)... und da muss jeder Prozess gestoppt werden ...

Ein Environment.Exit oder so halte ich für zu krass.

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

I
1.739 Beiträge seit 2005
vor 18 Jahren

Ich sehe den Einsatz dieses Samples eher um einen einzelnen Prozess zu visualisieren

Sehe ich genauso. Genau dafür macht man sowas.

Wenn ich die einzelnen Threads prüfen wollte müsste ich ein Array der BackGround-Objekte halten (was ich hier der einfachheit halber nicht gemacht habe)...

Ist aber somit nicht komplett. Ein abgestürzter Thread blockiert das Beenden der Applikation. Darauf wollte ich nur hinweisen, nichts weiter.

Ein Environment.Exit oder so halte ich für zu krass.

Würde ich für notwendig halten, um keine "Leichen" zu hinterlassen.

_
416 Beiträge seit 2005
vor 18 Jahren

Hallo,

ich hatte mal eine Applikation von mir so komplett auf Multithreading umgestellt. Leider hat mir das beim Umstellen auf .Net 2.0 übel mitgespielt. Ich bekomme ständig von Controls auf denen Drag'n'Drop aktiviert ist eine Exception, dass meine Applikation nur einen MessageLoop haben darf. Ich hatte leider noch nicht die Zeit das weiter zu testen (die Applikation ist noch im Umbau), aber die Exceptions bin ich nur losgeworden in dem ich wieder auf einen einzigen MessageLoop umgestellt hab. Wohlgemerkt: unter .Net1.1 lief alles prima.

Wie gesagt, ich konnte es noch nicht weiter testen, aber mich würde schon interessieren ob bei dir ähnliche Proleme aufgetaucht sind.

cu, tb

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

Original von tb

Wie gesagt, ich konnte es noch nicht weiter testen, aber mich würde schon interessieren ob bei dir ähnliche Proleme aufgetaucht sind.

cu, tb

Finde ich sehr wichtig.... das ganze ist als Spielerei auf 2003 entstanden.... falls es aber mit 2005 Probleme geben sollte werde ich mir sehr gut überlegen müssen ob ich so was überhaupt einsetze...

Edit: Ich arbeite bisher ausschliesslich mit 2003

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

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo zusammen,

ich habe so eine Konstruktion unter .NET 2.0 völlig ohne Probleme eingesetzt.

herbivore

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

Original von herbivore
Hallo zusammen,

ich habe so eine Konstruktion unter .NET 2.0 völlig ohne Probleme eingesetzt.

herbivore

Kannst Du mal testen ob dies nur ein DragDrop-Problem ist (wenn man etwas von einem Thread in den anderen rüberzieht) ?

Danke 😉

Edit: Die Idee war übrigens von herbivore 👍

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

_
416 Beiträge seit 2005
vor 18 Jahren

Hallo,

also nocheinmal, wie es bei mir passiert ist:

Ich habe eine Form mit TreeView (und vielen vielen anderen Controls). Bei diesem TreeView war AllowDrop auf true gesetzt. Die Applikation selbst war als MTAThread gekennzeichnet.

Als nun die Form instanziiert wurde kam eine Exception, dass Drag'n'Drop nur bei SingleThread-Applikationen aktiviert sein darf. Also MTAThread durch STAThread ersetzt. Nun kam eine Exception beim eintreten in den zweiten MessageLoop, dass dies nur bei MultiThread-Applikationen erlaubt ist. (die Applikation hatte standardmäßig mind. 2, einen MainMessageLoop mit ApplicationContext und einen jedes (Haupt-)Fenster). Letztere Fehlermeldung kam sogar wenn ich die AllowDrop-Eigenschaft des TreeViews auf false gesetzt und wieder MTAThread hingeschrieben hatte. Also noch unlogischer als überhaupt schon. (Soll STA und MTA nicht nur was mit COM-Komponenten zu tun haben?)

Mir hats an der Stelle gereicht, weil ich anderes zu tun hatte. Ich hab die Applikation vorerst auf ein Fenster umgestellt. Aber verstanden hab ich das nicht.

Wenn ich irgendwann mal wieder Zeit finde, werde ich das mal weiter testen. Leider werde ich dazu wieder Zeit finden müssen. X(

@Herbivore:
dass es bei dir funktioniert macht mir ja Mut. Ich denke sowieso dass es irgendwie mit der Umstellung von von .Net1.1 auf 2.0 zu tun hat. Microsoft hat zwar schon die Interfaceänderungen beschrieben, aber die logischen Änderungen schön verheimlicht. Das war mir auch noch an anderer Stelle auf die Füße gefallen. Ja, in VS2005 importieren und kompilieren geht ruck zuck, aber das heißt nicht dass die Applikation noch so läuft wie vorher. 🙁

cu, tb

_
416 Beiträge seit 2005
vor 18 Jahren

Hallo,

ich hab heute endlich nochmal etwas Zeit gefunden um das von mir beschriebenene Problem zu testen. Und es ist immernoch so wie ich es sagte. So lange kein Control AllowDrop=true gesetzt hat funktioniert alles ohne Probleme. Aber so bald auch nur ein einziges Drag'nDrop aktiviert, kommt es zu folgender Exception.

äußere Exception

DragDrop registration did not succeed.

innere Exception

Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it

Allerdings versteh ich das nicht, denn die Kommunikation mit anderen Prozessen, ergo auch anderen Threads, funktioniert ja schließlich. Also entweder mache ich etwas falsch (aber ich weiß echt nicht was) oder es geht so wirklich nicht.

Naja, ich werd das ganze jetzt mit dem Request-Response-Pattern umgehen. Das macht Dinge zwar komplizierter, aber die GUI noch performanter. Wars wieder nix mit der schnellen und einfachen Lösung =)

cu, tb

49.485 Beiträge seit 2005
vor 18 Jahren

Hallo tb,

laut Exception musst du Thread.ApartmentState auf ApartmentState STA setzen. Siehe auch Clipboard und System.Threading.ThreadStateException .

herbivore

_
416 Beiträge seit 2005
vor 18 Jahren

Hi,

hab's ausprobiert, schon funktionierts.

Thx. Ich hätte mal aufmerksamer das Board mitverfolgen sollen, naja.

Aber auf so eine Idee wäre ich gar nicht gekommen. Hab mir mal das .Net 2.0 Changelog durchgelesen, und an diese nicht grad kleine Änderung würde ich mich sicher erinnern.

cu, tb

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

Original von tb
Aber auf so eine Idee wäre ich gar nicht gekommen. Hab mir mal das .Net 2.0 Changelog durchgelesen, und an diese nicht grad kleine Änderung würde ich mich sicher erinnern.

Dies war auch schon in 1.1 so 🙂 .... Dort hat es zwar ev. nicht gekracht, was aber nicht heissen muss, dass es (nur weil es nicht kracht) unter 1.1 korrekt war.

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

X
40 Beiträge seit 2005
vor 18 Jahren

Um mal ein wenig Licht in die ganze Sache zu bringen:

Drag and Drop läuft ebenso wie z.B. das Clipboard noch über COM-Interop. Dazu wird intern OleInitialize() aufgerufen, welches ein STA erfordert. Deshalb, und für den Fall das eine Windows Forms Applikation ein ActiveX-Control hostet, welches per Definition ein STA-Objekt ist, wird auch die Main()-Methode von Windows Forms Applikationen automatisch von VS mit [STAThread] markiert.

Aus der MSDN:

A call to OleInitialize on an apartment that was previously initialized as multithreaded will fail[...]