Laden...

Ausführung ohne Dispatcher-Instanz an den GUI-Thread delegieren

Erstellt von WhiteGloves vor 14 Jahren Letzter Beitrag vor 14 Jahren 3.605 Views
W
WhiteGloves Themenstarter:in
109 Beiträge seit 2009
vor 14 Jahren
Ausführung ohne Dispatcher-Instanz an den GUI-Thread delegieren

Hallo Leute,

ich habe ein kleines Problem mit meiner Multithread Anwendung...

Fehlermeldung lautet:
"Beim aufrufenden Thread muss es sich um einen STA-Thread handeln, da dies für viele Komponenten der Benutzeroberfläche erforderlich ist."

Die Situation ist folgende:
Ich habe ein Window welches nach klick auf einen Button ein Objekt einer Klasse instanziert.
Innerhalb des Objekts werden nun mehrere Threads gestartet in denen wiederum weitere Objekte anderer Klassen instanziert werde...
Tritt innerhalb dieser Objekte ein Fehler auf, möchte ich ein selbst erstelltes Window anzeigen und dort die Fehlermeldung ausgeben.

Da man keine Controls / Windows aus dem Nicht-GUI-Thread erzeugen kann, kommt denke ich mal diese Fehlermeldung.
Allerdings komme ich auf keine Idee bzw Umsetzung wie ich das umgehen kann.

Meine erste Idee war es die Erstellung des Windows in eine Methode zu verlagern und diese via this.Dispatcher.Invoke() aufzurufen.
Aber es steht mir in dem Objekt keine this.Dispatcher Instanz zur Verfügung...

Ich habe mir mal den Weg über Events angeguckt, allerdings habe ich noch nie ein eigenes Event erstellt.
Habe es dennoch mal versucht aber meine Versuche schnell wieder verworfen weil ich nicht verstand was ich tat... 😦

Kann mir jemand weiterhelfen?

PS: Kennt jemand ein gutes einfaches Beispiel für eigene Events was man sich mal anlesen könnte?
Was ich bei Google finde ist mir immer zu komplex für einen solch einfachen (?) fall.

MfG
WhiteGloves

Hallo, ich heiße Hein Blöd und bin Softwareentwickler

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo WhiteGloves,

alles was du zu Events wissen musst, steht in der FAQ. Übersichtlich, einfach und verständlich. Siehe [FAQ] Eigenen Event definieren.

Und Events sind wirklich eine Lösung für dein Problem. An den GUI-Thread sollte man nur aus dem GUI-Code delegieren. Es ist also sinnvoll, dass die Kontrolle per Event erst an einen EventHandler in einer GUI-Klasse übergeben wird. Und diese GUI-Klasse hat dann ja eine Dispatcher Instanz parat.

Siehe auch

[FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke)
Eleganteste Methode: Aus Thread auf Controls einer Form zugreifen (ist Windows-Forms, aber gilt alles bei WPF analog)

herbivore

W
WhiteGloves Themenstarter:in
109 Beiträge seit 2009
vor 14 Jahren

Aus dem Objekt preader der Klasse clsPropertyReader Feuere ich den ErrorOccurred Event um damit in meiner Test Klasse eine Aktion auszulösen.

Leider ist _if (errorOccurred != null) bei mir immer Null...

public class clsPropertyReader
{
	public event EventHandler ErrorOccurred;

	protected virtual void OnErrorOccurred(EventArgs e)
	{
		EventHandler _errorOccurred = ErrorOccurred;
		if (_errorOccurred != null)
		{
			_errorOccurred(this, e);
		}
	}

	private string getProp(string propname)
	{
			OnErrorOccurred(EventArgs.Empty);
	}
}


public class Test
{
	clsPropertyReader preader = clsPropertyReader();
	preader.ErrorOccurred += preader_ErrorOccurred;

	public void preader_ErrorOccurred(object sender, EventArgs e)
	{
		System.Windows.MessageBox.Show("Testing");
		winError ErrorWin = new winError("Teststring");		
	}  
  
  }


}

Allerdings ergibt sich noch ein weiteres Problem.
Wenn der Fehler gefunden wird und in meiner Test Klasse, die das Event aboniert, der Event auch wirklich ankommt, befände ich mich immer noch nicht im GUI Thread...
(Ich muss quasi noch eine "Ebene" höher kommen um im GUI Thread zu landen)
Wie löst man das am besten?
Ich könnte zwar einen weiteren Event hinzufügen, der dann hoch zum GUI geht...
Aber geht das nicht eleganter?

Hallo, ich heiße Hein Blöd und bin Softwareentwickler

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo WhiteGloves,

der EventHandler muss für genau das Objekt registriert sein, für das der Event ausgelöst wird.

Um einen Event ins GUI zu bekommen, kann es nötigt sein, dass zwischengeschaltete Objekte den ursprünglichen Event fangen und dann selbst einen eigenen Event auslösen.

herbivore

W
WhiteGloves Themenstarter:in
109 Beiträge seit 2009
vor 14 Jahren

Ah okay, das beantwortet quasi meine Frage aus meiner PM...

Allerdings muss das doch eleganter gehen?
Man stelle sich vor das man aus einem Objekt heraus an den GUI Thread will und man dazu durch 10 Objekte hindurch den Event durchgeben muss... Das ist ja voll umständlich 😦

Hallo, ich heiße Hein Blöd und bin Softwareentwickler

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo WhiteGloves,

Man stelle sich vor das man aus einem Objekt heraus an den GUI Thread will und man dazu durch Objekte hindurch den Event durchgeben muss...

das wird selten vorkommen.

Das ist ja voll umständlich 😦

Wie umständlich das ist, hängt davon ab, von wievielen unterschiedlichen Klassen die Objekte sind. Wenn es nur eine Klasse ist, braucht man vom Code her nur einen Event zu schreiben, auch wenn der zur Laufzeit zehnmal von Objekt zu Objekt gefeuert wird.

herbivore

W
WhiteGloves Themenstarter:in
109 Beiträge seit 2009
vor 14 Jahren

Ich habe in meinem Programm zur Zeit ein SQL Objekt welches wie der Name schon verrät Aktionen an einer Datenbank durchführt...

Dieses Objekt instanziere ich an verschiedenen Stellen im Code (weil es unterschiedliche Datenbanken (bzw Server) sind auf die ich Connecte, daher muss ich mehrfach instanzieren und kann das Objekt nicht immer weiterreichen)

Ich müsste quasi jedes man wenn ich ein SQL Objekt instanziere das Event abonieren und zusehen das es bis zum GUI Thread hochgeschoben wird...
In meiner Anwendung glaube ich das ich meistens 3 Schritte benötige...

Ich habe mal bei WPF etwas gelesen über blubbernde Events.
Die von unten nach oben durchblubbern also durchgereicht werden... Kann ich das hier nicht irgendwie verwenden?

Hallo, ich heiße Hein Blöd und bin Softwareentwickler

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo WhiteGloves,

Kann ich das hier nicht irgendwie verwenden?

das bezieht sich ja nur auf die GUI-Events. Das kann man benutzen, wenn z.B. ein Form auch direkt die Eingaben mitbekommen soll, die in einer im Form enthaltenen TextBox gemacht werden. Ich sehe nicht, wie das bei deinem Problem helfen kann. Es geht ja bei dir nicht darum, Events zwischen WPF-Elementen durchzureichen, sondern durch eigene Klassen.

herbivore

U
1.688 Beiträge seit 2007
vor 14 Jahren

Hallo,

man kann das event auch etwas einfacher durchreichen, dafür gibt's eine Syntax ähnlich der von Properties:
Event Accessors

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo ujr,

zwar könnte man über diese Syntax im Prinzip einen EventHandler, der für das übergeordnete Objekt registriert wird, bei einem untergeordneten Objekt registrieren, so dass ein im untergeordneten Objekt gefeuerter Event ohne Umwege beim EventHandler ankommt (darauf wolltest du doch hinaus, oder?), nur stimmt dann leider der sender-Parameter nicht mit dem überein, was der EventHandler erwarten darf. Der erwartet ja als Sender das Objekt, bei dem er den Event registriert hat, es kommt aber ein Objekt, welches er überhaupt nicht kennt. Daher verbietet sich diese Vorgehensweise.

herbivore

U
1.688 Beiträge seit 2007
vor 14 Jahren

Hallo herbivore,

(darauf wolltest du doch hinaus, oder?)

Ja, genau.

nur stimmt dann leider der sender-Parameter nicht mit dem überein, was der EventHandler erwarten darf. Der erwartet ja als Sender das Objekt, bei dem er den Event registriert hat,

Hmmm - ist die Konvention bzw. Regel derart? Einerseits hast Du ganz klar recht mit

es kommt aber ein Objekt, welches er überhaupt nicht kennt.

Das ist ein Punkt, den ich nicht ausreichend bedacht habe. Aber andererseits kann es doch sinnvoll sein, die tatsächliche Quelle zu erfahren. Sollte man das dann nur über Properties in den EventArgs übermitteln?

Oft braucht man das ja nicht - ich bisher 1x. Es schien mir doch eine ganz nützliche "Abkürzung" für eigene, in sich abgeschlossene Strukturen zu sein - selbst in Bibliotheken könnte man sich nach "außen" an "andere Regeln" halten. Aber klar, dann wird's weniger wartbar.

49.485 Beiträge seit 2005
vor 14 Jahren

Hallo ujr,

Hmmm - ist die Konvention bzw. Regel derart?

ob das [der Sender immer das Objekt sein muss, bei man den Event registriert hat] jetzt irgendwo explizit als Regel steht, weiß ich nicht, aber eine Konvention ist es auf jeden Fall. Als sender kommt nur das Objekt in Betracht, bei dem man den Event abonniert hat. Das kann man z.B. in Windows Forms sehen: Wenn du im Form KeyDown abonnierst und KeyPreview auf true setzt und dann in einer TextBox auf dem Form Text eingibst, dann ist der Sender das Form und nicht die TextBox, denn man hat ja das Event des Forms und nicht das der TextBox abonniert.

Ob es praktisch wäre, die tatsächliche Quelle zu erfahren, steht auf einem anderen Blatt und hängt auch von den genauen Umständen ab. Wenn der sender z.B. eine interne oder private Klasse ist, wäre die Bekanntgabe mindestens ungünstig, wenn nicht sogar schädlich. In dem KeyPreview-Beispiel könnte es dagegen schon praktisch sein, die tatsächliche Quelle zu kennen. Aber das müsste man dann wirklich über die EventArgs machen.

herbivore

W
WhiteGloves Themenstarter:in
109 Beiträge seit 2009
vor 14 Jahren

Ich will euch ja nicht aus eurer Diskussion reißen 😉
Aber ich habe das jetzt so gemacht das ich das Event einfach bis in die GUI Klasse weiter durchreiche.

Nun bin ich in der GUI Klasse angekommen, befinde mich aber immer noch in einem Thread.
Deshalb habe ich nun folgendes:


void MVFile_ErrorOccurred(object sender, clsEventArgs e)
{
	this.Dispatcher.Invoke(new System.Windows.Forms.MethodInvoker(delegate() 
		{
			winError ErrorWin = new winError(e); 
		}));
}

Die Funktion wird aufgerufen wenn das EVent ausgelößt wurde...
Jetzt habe ich wie üblich gebrauch von dem MethodInvoker gemacht und dort via Delegate mein Window erstellt.
Allerdings habe ich dabei noch nie Parameter übergeben...
Nun muss ich aber das clsEventArgs e Objekt dem winError übergeben...

Ich habe das mal so versucht:


this.Dispatcher.Invoke(new System.Windows.Forms.MethodInvoker(delegate() 
                {
                    winError ErrorWin = new winError(e); 
                }), new object[] { e });

aber leider ohne erfolg, bekomme die Meldung "Parameteranzahlkonflikt".

Hallo, ich heiße Hein Blöd und bin Softwareentwickler

6.862 Beiträge seit 2003
vor 14 Jahren

Hallo,

So wie du es jetzt machst, also den Thread in Form des Eventhandlers bis zur GUI durchreichen, ist vollkommen okay. Machen ja z.B. die verschiedenen Timer ebenfalls, wenn es nicht grad der DispatcherTimer ist.

Aber zu deinem Invoke: Schau dir mal die Beispiele in der Doku an. Wieso willst du nen MethodInvoker aus Windows Forms in WPF verwenden?

Baka wa shinanakya naoranai.

Mein XING Profil.

W
WhiteGloves Themenstarter:in
109 Beiträge seit 2009
vor 14 Jahren

Weil es irgendwie eine schöne Zeile ist 😉
Man muss für eine Code Zeile nicht extra einen Delegate erstellen und braucht auch keine zusätzliche Methode die mir das winError Fenster anzeigt.

Hallo, ich heiße Hein Blöd und bin Softwareentwickler

6.862 Beiträge seit 2003
vor 14 Jahren

Dafür mischt du zwei völlig unabhängige GUI Technologien grundlos und musst Assemblies referenzieren die du gar nicht brauchst. Na toll 😃

Kannst du auch mit anonymen Delegates oder Lambda Expressions arbeiten.

Baka wa shinanakya naoranai.

Mein XING Profil.

U
1.688 Beiträge seit 2007
vor 14 Jahren

Kannst du auch mit anonymen Delegates oder Lambda Expressions arbeiten.

Oder Action<> verwenden