Hallo zusammen,
Mein System:
Windows XP - SP 2
Visual Studio 2008 Team System
.Net 3.5 - Framework
etc.
Folgende Situation:
Das Problem:
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.
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
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.
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.
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);
}
}
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.
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);
// ...
}
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.
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.
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.
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!
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.
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;
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.