Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

Problem: Zwei Asynchrone durchläufe - Identisch und doch verschieden
Ruben
myCSharp.de - Member

Avatar #avatar-2971.jpg


Dabei seit:
Beiträge: 61
Herkunft: Hamburg

Themenstarter:

Problem: Zwei Asynchrone durchläufe - Identisch und doch verschieden

beantworten | zitieren | melden

Hallo zusammen,

Mein System:
Windows XP - SP 2
Visual Studio 2008 Team System
.Net 3.5 - Framework
etc.

Folgende Situation:
- Eine Form hat zwei ComboBoxen.
- Beide ComboBoxen werden asynchron gefüllt.
- Aufruf der asynchronen Methoden läuft über einen Handler bzw. Delegaten
und wird über BeginInvoke gestartet.
- Um das Befüllen der ComboBoxen zu synchronisieren nutze ich AsyncOperation.
- Die asynchrone Methode für die erste ComboBox wird im Load-Event gestartet.
- Im Load-Event wird zuerst ein AsyncOperation im AsynOperationManager erzeugt.
- Das AsyncOperation-Objekt wird dann an die asynchrone Methode übergeben.
- Die erste ComboBox wird über die Post-Methode erfolgreich und asynchron gefüllt.

Das Problem:
- Die zweite ComboBox wird im Leave-Event der ersten ComboBox gefüllt.
- Beim Aufrufen der Post-Methode wird der Vorgang in einem anderen Thread ausgeführt und es wird eine Exception geworfen.
(Threadübergreifender Vorgang)

Das Prinzip bei der zweiten ComboBox ist dasselbe, aber es funktioniert igendwie nicht.
Erst dachte ich, dass die Form keine Post-Aufrufe verarbeiten kann, weil ich einen Wartepunkt im Event setze - sollte ein anderer Prozess bereits laufen -, aber die Form hat nicht gewartet bzw. diesen Punkt nicht erreicht.

EDIT:
Zur Lösung springen
In der Zeit vor fünf Minuten ist Jetzt die Zukunft. Jetzt ist die Gegenwart. Die Zeit, in der ich zu erzählen begonnen habe, ist die Vergangenheit von Jetzt und die Zukunft von der Gegenwart der Zeit, fünf Minuten bevor ich zu erzählen begann.
private Nachricht | Beiträge des Benutzers
herbivore
myCSharp.de - Experte

Avatar #avatar-2627.gif


Dabei seit:
Beiträge: 49.486
Herkunft: Berlin

beantworten | zitieren | melden

Hallo Ruben,

warum du AsyncOperation verwendest, ist mir unklar. Mach es besser wie in [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) beschrieben.

herbivore
private Nachricht | Beiträge des Benutzers
chilic
myCSharp.de - Experte



Dabei seit:
Beiträge: 2.111

beantworten | zitieren | melden

Geht es hier wirklich um so viele Einträge dass ein synchrones Befüllen nicht geht, oder dauert nur das Laden der Daten so lange?
Im zweiten Fall könntest du ja nur das Laden asynchron machen und das befüllen dann "normal", dann hast du schon das ganze Threadübergreifende mit Control nicht mehr.
private Nachricht | Beiträge des Benutzers
Tarion
myCSharp.de - Member



Dabei seit:
Beiträge: 381

beantworten | zitieren | melden

Du kannst keine Asynchronen GUI-Zugriffe machen, also auch die GUI nicht aus 2 Threads aktualisieren ohne beim eigentlichen GUI Zugriff (dem aktualisieren) wieder zu synchronisieren (BeginInvoke).

Dein vorhaben klingt komplexer als es sein müsste. Normal sieht es etwa so aus:

Thread1: Stellt Daten bereit und ruft von der GUI AddData() auf.
Thread2: Stellt Daten bereit und ruft von der GUI AddData() auf.

In der GUI ruft die AddData() Funktion dann (falls nötig) BeginInvoke auf und wird so mit dem GUI Thread synchronisiert.

Genaueres siehe herbivore Verweis.
private Nachricht | Beiträge des Benutzers
Ruben
myCSharp.de - Member

Avatar #avatar-2971.jpg


Dabei seit:
Beiträge: 61
Herkunft: Hamburg

Themenstarter:

beantworten | zitieren | melden

Zitat von chilic
Geht es hier wirklich um so viele Einträge dass ein synchrones Befüllen nicht geht, oder dauert nur das Laden der Daten so lange?
Im zweiten Fall könntest du ja nur das Laden asynchron machen und das befüllen dann "normal", dann hast du schon das ganze Threadübergreifende mit Control nicht mehr.
Synchron hatte ich das vorher, aber es gibt immer kurze Ladezeiten (ca. 1-2 Sek) und da ist eine "gefühlte Ewigkeit" ^^.

Und einmal danke für die anderen schnellen Antworten. ;)

Mir ist bekannt, dass man mit Control.Invoke am besten diese Synchronisierung durchführt, doch bei meinem Beispiel tretet das Problem auf, dass es beim einen mit AsyncOperation funktioniert und beim anderen nicht. Das wird mir einfach nicht klar. Ich habe auch schon gedacht, dass es daran liegt, dass im Load-Event keine threadübergreifenden Vorgänge gemeldet werden, aber das war es auch nicht, denn da wird sauber synchronisiert.

EDIT:
Habe eine Änderung gemacht und es vorerst synchron laufen lassen
Bisher keine Verzögerung, aber die Lösung für das Problem würde mich dennoch interessieren.


        private void cboProduktnummer_Leave(object sender, EventArgs e)
        {
            Produkt p = (Produkt)this.cboProduktnummer.SelectedItem;
            if (p == null)
            {
                this.cboAuftragsnummer.Items.Clear();
                this.current = null;
                return;
            }

            if (p.Equals(this.current))
                return;
            else
            {
                // synchron
                this.cboAuftragsnummer.Items.Clear();
                this.cboAuftragsnummer.Items.AddRange(p.Auftrage.Values.ToArray());
                this.current = p;

                // asynchron
                //this.AuftragComboBoxInit(p);
            }
        }
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Ruben am .
In der Zeit vor fünf Minuten ist Jetzt die Zukunft. Jetzt ist die Gegenwart. Die Zeit, in der ich zu erzählen begonnen habe, ist die Vergangenheit von Jetzt und die Zukunft von der Gegenwart der Zeit, fünf Minuten bevor ich zu erzählen begann.
private Nachricht | Beiträge des Benutzers
Ruben
myCSharp.de - Member

Avatar #avatar-2971.jpg


Dabei seit:
Beiträge: 61
Herkunft: Hamburg

Themenstarter:

beantworten | zitieren | melden

Da es nicht geeignet ist Code-Dateien hochzuladen, muss ich wohl oder übel den Platz im Forum ausnutzen ;)

Ich habe jetzt den Code so gut wie möglich gekürzt. Ich hoffe übersichtlich.

EDIT:
Es handelt sich um eine ComboBox in der Autragsnummern angezeigt werden.
AuftragComboBoxInit steht dafür, dass das Eintragen der Werte in die ComboBox initiiert werden soll.
Hoffe, dass es nachvollziehbar ist ^^



        // --------------------------------------------------------- //
		// Diese Methode befüllt die zweite ComboBox NICHT erfolgreich.
		// Alle Post-Methoden werden in einem DRITTEN Thread ausgeführt.
		// --------------------------------------------------------- //
        private void AuftragComboBoxInit(Produkt p)
        {
			// INFO: GUI-Thread ID - [1]
			System.Diagnostics.Debug.Print("START - GUI-Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
			
			// ...
			
			// Erstellen einer AsyncOperation um die Vorgänge beim Befüllen der ComboBox zu synchronisieren
            AsyncOperation asyncOp = AsyncOperationManager.CreateOperation("auftragComboBox");
			
			// ...
			
			// INFO: eine weitere annonyme Methode wird definiert ^^
            MethodInvoker m =
                delegate()
                {
					// ...

					// INFO: Die ID ist definitiv anders als die vom aufrufenden GUI-Thread - [5]
					System.Diagnostics.Debug.Print("ANFANG - Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);

					// Dieses WaitHandle dient dazu, die Post-Aufrufe zu synchronisieren,
					// sonst würde die Methode schon abgelaufen sein, bevor der erste Post vom GUI-Thread verarbeitet wird.
                    System.Threading.AutoResetEvent postWait = new System.Threading.AutoResetEvent(false);

					// ...
					
                    for (int i = 0; i < number; i++)
                    {
						// ...
						
						// Post wird aufgerufen um die Daten in die ComboBox zu bringen
                        asyncOp.Post(
                            delegate(object args)
                            {
                                try
                                {
									// INFO:
									// Wenn der Wert FALSE ist, dann brauch die Aktion nicht ausgeführt werden
									//
									// IMPORTANT:
									// Hier tretet der entscheidende Fehler auf,
									// obwohl der Aufbau derselbe ist wie im ersten Durchlauf der ersten ComboBox
                                    this.cboAuftragsnummer.Items.AddRange(datenArray);
                                }
                                catch (Exception)
                                {
									// IMPORTANT:
									// Es ist eine Exception wegen eines threadübergreifenden Vorgangs aufgetreten.
									// Die ID dieses Threads ist [4]
									// (HINWEIS: [4] ist dieselbe ID wie beim Thread in "ProduktComboBoxInit()")
									// (Werden IDs also doppelt vergeben, wenn der eine Thread nicht mehr ausgeführt wird?)
                                    System.Diagnostics.Debug.Print("ERROR - Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
                                }
								
								// Signalisiert, dass der Thread seinen Vorgang fortsetzen kann.
                                postWait.Set();
                            }, null);
							
						// Thread wartet bis der Post abgeschlossen wurde.
                        postWait.WaitOne();
						
						// Kurze Pause ;)
						System.Threading.Thread.Sleep(0);
						
						// ...
                    }

                    // ...

					// OperationComplete wird mit dem letzten Post ausgeführt
                    asyncOp.PostOperationCompleted(
                        delegate(object args)
                        {
                            try
                            {
								// INFO:
								// Wenn der Wert FALSE ist, dann brauch die Aktion nicht ausgeführt werden
								//
								// IMPORTANT:
								// Hier tretet der entscheidende Fehler auf, obwohl der Aufbau derselbe ist wie in "ProduktComboBoxInit()"
                                if (lastA != null)
                                    this.cboAuftragsnummer.Items.AddRange(datenArray);
                            }
                            catch (Exception)
                            {
								// IMPORTANT: (prinzipiell dasselbe wie oben)
								// Es ist eine Exception wegen eines threadübergreifenden Vorgangs aufgetreten.
								// Die ID dieses Threads ist [4]
								// (HINWEIS: [4] ist dieselbe ID wie beim Thread in "ProduktComboBoxInit()")
								// (Werden IDs also doppelt vergeben, wenn der eine Thread nicht mehr ausgeführt wird?)
                                System.Diagnostics.Debug.Print("ERROR - Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
                            }
							
							// Signalisiert, dass der Thread seinen Vorgang fortsetzen kann.
                            postWait.Set();
                        }, null);
						
					// Thread wartet bis PostOperationCompleted abgeschlossen wurde.
                    postWait.WaitOne();
					
					// das WaitHandle wird freigegeben
					postWait.Close();
					
					// Die ID dieses Thread ist [5]
                    System.Diagnostics.Debug.Print("ENDE - Thread: {0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
                };

			// Startet den asynchronen Vorgang
			m.BeginInvoke(delegate(IAsyncResult result)
			{
				m.EndInvoke(result);                    
			}, null);
			
			// ...
        }
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Ruben am .
In der Zeit vor fünf Minuten ist Jetzt die Zukunft. Jetzt ist die Gegenwart. Die Zeit, in der ich zu erzählen begonnen habe, ist die Vergangenheit von Jetzt und die Zukunft von der Gegenwart der Zeit, fünf Minuten bevor ich zu erzählen begann.
private Nachricht | Beiträge des Benutzers
ujr
myCSharp.de - Experte



Dabei seit:
Beiträge: 1.688

beantworten | zitieren | melden

Hallo,

ist denn überhaupt sichergestellt, das "AuftragComboBoxInit" (seltsamer Name, übrigens) im GUI-Thread ausgeführt wird? Erzeuge doch mal testweise die verwendete "AsyncOperation" auch im Load-Ereignis.

Ansonsten sieht der Code sehr nach [FAQ] Warum blockiert mein GUI? Abschnitt "Die Falle" aus.
private Nachricht | Beiträge des Benutzers
Ruben
myCSharp.de - Member

Avatar #avatar-2971.jpg


Dabei seit:
Beiträge: 61
Herkunft: Hamburg

Themenstarter:

beantworten | zitieren | melden

Zitat von ujr
ist denn überhaupt sichergestellt, das "AuftragComboBoxInit" (seltsamer Name, übrigens) im GUI-Thread ausgeführt wird?
Ja, ich habe die ThreadID am Anfang der Methode geprüft. GUI-Thread war ID 1
Die asynchrone Methode hatte die Thread ID 5.
Im Post war es dann die Thread ID 4 - also ein dritter Thread.
Zitat von ujr
Erzeuge doch mal testweise die verwendete "AsyncOperation" auch im Load-Ereignis.
Die Methode kann ich leider nicht so leicht in das Load-Ereignis schieben, da sie auf den ausgewählten Wert in der ersten ComboBox reagiert.

ABER jetzt aufgepasst
Ich habe stattdessen die Methode im Load-Ereignis (der ersten ComboBox), welche vom Prinzip her dasselbe wie die gepostete Methode macht, in ein Click-Ereignis von einem Button gepackt.
Jetzt ratet mal was passiert ist...
Dasselbe Problem wie bei der zweiten ComboBox!
- GUI-Thread ist ID 1
- asynchrone Methode ist ID 4
- Post-Thread ist ID 5
(gut - die IDs sind andersrum ;)

Schlussfolgerung:
Alle asynchronen Vorgänge, die im Load-Ereignis initiiert werden, werden beim Aufruf von Post an den GUI-Thread geleitet.
Alle anderen Ereignisse (bisher getesteten zumindest) können die erstellten AsyncOperation.Post-Aufrufe nicht an den GUI-Thread leiten.

Ursache:
Aus mir nicht bekannten Gründen kann nur das im Load-Ereignis erstellte AsyncOperation-Objekt erfolgreich alle Post-Aufrufe an den GUI-Thread weiterleiten. Wenn das AsyncOperation-Objekt in einem anderen Ereignis erzeugt wird, das schließlich auch von demselben Thread, wie im Load-Ereignis ausführt wird (geprüft: ManagedThreadID;Thread.Name;ContextID), dann funktioniert der Aufruf der Post-Methode nicht bzw. er wird in einem dritten Thread ausgeführt.

Lösung:
Am beste ein AsyncOperation-Objekt als private-Field in der Form oder Klasse definieren und alle Post-Aufrufe auf dasselbe AsyncOperation-Objekt konzentrieren.
VORSICHT: Kein AsyncOperation.OperationComplete oder .PostOperationComplete aufrufen, dann wird das AsyncOperation-Objekt unbrauchbar. Am besten erst in Dispose oder Closing aufrufen.

(bitte um Korretur, falls notwendig, ansonsten werde ich dieses Thema als GELÖST setzen - wenn jemand eine Erklärung hat, dann wäre das auch sehr gut ;)
In der Zeit vor fünf Minuten ist Jetzt die Zukunft. Jetzt ist die Gegenwart. Die Zeit, in der ich zu erzählen begonnen habe, ist die Vergangenheit von Jetzt und die Zukunft von der Gegenwart der Zeit, fünf Minuten bevor ich zu erzählen begann.
private Nachricht | Beiträge des Benutzers
ujr
myCSharp.de - Experte



Dabei seit:
Beiträge: 1.688

beantworten | zitieren | melden

Zitat von Ruben
Ja, ich habe die ThreadID am Anfang der Methode geprüft. GUI-Thread war ID 1

Wer sagt, dass es 1 sein muss?

Welchen Wert hat b, wenn Du die folgende Zeile an den Anfang von AuftragComboBoxInit schreibst:


bool b=InvokeRequired;
private Nachricht | Beiträge des Benutzers
Ruben
myCSharp.de - Member

Avatar #avatar-2971.jpg


Dabei seit:
Beiträge: 61
Herkunft: Hamburg

Themenstarter:

beantworten | zitieren | melden

Zitat von ujr
Welchen Wert hat b, wenn Du die folgende Zeile an den Anfang von AuftragComboBoxInit schreibst:


bool b=InvokeRequired;

Gut, das mit der ID war nur ein Testbeispiel, aber hier die Auflösung ;)


bool b = this.InvokeRequired;
System.Diagnostics.Debug.Print("b ist [{0}]", b);

Ergebnis:

b ist [False]
Nach der MSDN ist alles richtig.
Und die AuftragComboBoxInit-Methode wird auch direkt im ComboBox_Leave-Ereignis aufgerufen (ist mein Fehler, dass das leider nicht so ersichtlich war).
In der Zeit vor fünf Minuten ist Jetzt die Zukunft. Jetzt ist die Gegenwart. Die Zeit, in der ich zu erzählen begonnen habe, ist die Vergangenheit von Jetzt und die Zukunft von der Gegenwart der Zeit, fünf Minuten bevor ich zu erzählen begann.
private Nachricht | Beiträge des Benutzers