Hallo,
Ich habe ein Problem mit Signalen über Thread-Grenzen hinweg. Ich versuche jetzt erstmal das Konstrukt zu beschreiben, dass ich gebaut habe:
1. Ich habe einen Thread, der verschiedene Daten verarbeitet. Wenn keine neuen Daten da sind wartet er auf eine Waitcondition.
2. Ich habe eine GUI (aus dem Hauptprogramm raus gestartet), die Slider zur Verfügung stellt.
3. Immer wenn die Slider verstellt werden, wird die Slot-Funktion OnSliderChanged() in der GUI aufgerufen. Diese liest dann die neuen Werte aus.
4. Mittels der wakeOne()-Funktion der WaitCondition wird der Rechenthread zur Verarbeitung "geweckt".
5. Während der Thread rechnet, schickt er Signale an eine Berichtklasse. Dies dient zum Protokollieren der Rechendaten.
6. Die Berichtklasse läuft wiederum im Hauptthread der Anwendung (ich kann qDebug() daraus aufrufen, was ja nur in Threads mit GUI geht und GUI geht nur im Hauptthread)
Zum Problem:
Wenn ich den Slider recht schnell auf und ab bewege kommt es dazu, dass manche Rechenwerte nicht an die Berichtklasse gesendet werden. Das sehe ich an der Debug-Ausgabe in dieser Klasse. Dadurch kommt es zu Inkonsistenzen bei der Protokollierung und anschließend zum Absturz der Anwendung, weil die entsprechenden Elemente im Protokoll-Baum fehlen.
Zur Frage:
Ist es möglich, dass Signale bei der Interprozesskommunikation (QueuedConnection) bzw. ihre Daten verloren gehen?
Sonstiges:
Der Rechenthread selbst hat übrigens auch einiges zu tun, rechnet also teilweise langsamer als ich den Slider verstelle. Und in der Release-Variante, die ja selbst um einiges schneller rechnet, tritt das beschriebene Problem nicht auf. Kann sein, dass das Programm dann einfach besser hinterher kommmt. Blöd nur, wenn das Problem später auch da auftaucht...
Hat jemand eine Idee, woher das Problem kommen könnte?
Grüße,
Kay
Signale gehen verloren?
-
Christian81
- Beiträge: 7319
- Registriert: 26. August 2004 14:11
- Wohnort: Bremen
- Kontaktdaten:
Ohne Code der Schlüsselstellen ist es nicht möglich, das Problem zu lösen. Es ist offenbar auch nicht klar, wo (beim Übergeben der Daten an den Thread oder beim Zurücksenden der Resultate) die "Daten verloren gehen".
Folgende Hinweise:
- qDebug schreibt auf die Konsole und benötigt keine GUI.. du kannst damit auch den Thread "debuggen"...
- Die Datenübergabe an den Thread ist unklar. Wurde da ein Thread-sicherer Mechanismus verwendet?
- Wenn du schon Signals verwendest, um die Resultate zurückzusenden, warum verwendest du nicht auch Signals, um die Daten an den Thread zu senden (anstelle der WaitCondition)?
hth!
Folgende Hinweise:
- qDebug schreibt auf die Konsole und benötigt keine GUI.. du kannst damit auch den Thread "debuggen"...
- Die Datenübergabe an den Thread ist unklar. Wurde da ein Thread-sicherer Mechanismus verwendet?
- Wenn du schon Signals verwendest, um die Resultate zurückzusenden, warum verwendest du nicht auch Signals, um die Daten an den Thread zu senden (anstelle der WaitCondition)?
hth!
@solarix:
1: qDebug schreibt bei mir (unter Windows) auf eine TextEdit-Fenster. Dementsprechend kommt es zu Abstürzen, wenn ich es außerhalb des Hauptthreads ausführe (gerade eben nochmal probiert
) Der Thread kann da auch drauf schreiben, indem er seine Nachrichten an einen Debug-Slot im Hauptprogramm schickt.
2: Die Datenübergabe an den Thread erfolgt mittels einer gemeinsamen, durch ein Mutex geschützten Variable.
3: Wenn ich ein Signal mit den Daten an den Thread schicke, wird der Slot ja nicht im Rechenthread sondern im Hauptthread ausgeführt. Da müsste ich dann ebenso eine Waitcondition verwenden, um den Thread weiter arbeiten zu lassen und die Variablenzuweisung auch per Mutex schützen. Wäre in meinen Augen der selbe Aufwand.
4: Die Daten gehen auf dem Wege vom Rechenthread zur Berichtklasse verloren, wenn ich die Slider zu schnell bewege.
@Christian81:
Ich weiß, dass die Signale nicht verloren gehen können, aber offensichtlich tun sie zumindest sowas ähnliches, bzw. es sieht es so aus. Ich versuche mal ein minimales Beispiel zu schreiben:
Hier der Code für den Slot in der GUI für die Slider:
Hier der vereinfachte Rechenthread:
Und hier der Slot der Berichtklasse:
Wenn nun bei den hinzugefügten Daten in die Berichtklasse einmal ein Wert fehlt (also die Reihenfolge nicht eingehalten wird), so hat die letzte "Zeile" eine Spalte weniger und beim zurück iterieren ruft man Item->parent() so oft auf, bis es kein Elternparent mehr gibt und dieser Funktionsaufruf NULL zurück gibt. Der nächste Zugriff auf Item führt somit zu einem SIGSEGV.
Dass Daten nicht hinzugefügt werden und somit Spalten fehlen habe ich per Debugausgabe schon nachprüfen können.
1: qDebug schreibt bei mir (unter Windows) auf eine TextEdit-Fenster. Dementsprechend kommt es zu Abstürzen, wenn ich es außerhalb des Hauptthreads ausführe (gerade eben nochmal probiert
2: Die Datenübergabe an den Thread erfolgt mittels einer gemeinsamen, durch ein Mutex geschützten Variable.
3: Wenn ich ein Signal mit den Daten an den Thread schicke, wird der Slot ja nicht im Rechenthread sondern im Hauptthread ausgeführt. Da müsste ich dann ebenso eine Waitcondition verwenden, um den Thread weiter arbeiten zu lassen und die Variablenzuweisung auch per Mutex schützen. Wäre in meinen Augen der selbe Aufwand.
4: Die Daten gehen auf dem Wege vom Rechenthread zur Berichtklasse verloren, wenn ich die Slider zu schnell bewege.
@Christian81:
Ich weiß, dass die Signale nicht verloren gehen können, aber offensichtlich tun sie zumindest sowas ähnliches, bzw. es sieht es so aus. Ich versuche mal ein minimales Beispiel zu schreiben:
Hier der Code für den Slot in der GUI für die Slider:
Code: Alles auswählen
void GUI::OnSliderChanged(double Value)
{
Mutex->lock();
Factor = Slider->value(); //hier werden eigentlich mehrere Slider abgefragt, aber ist hier egal
ThreadWait->wakeOne();
Mutex->unlock();
}
double GUI::GetValue()
{
Mutex->lock();
double ReturnValue = Factor;
Mutex->unlock();
return ReturnValue;
}
Hier der vereinfachte Rechenthread:
Code: Alles auswählen
Thread::run()
{
while(Stop==false)
{
while((!ThreadWait.wait(&MutexData, 500)&&(Stop==false))
{
msleep(1);
}
NeuerWert = GUI->GetValue();
StelleBerechnungenAn();
emit NewBerichtData("Wert1", QString("%1").arg(Ergebnis1));
emit NewBerichtData("Wert2", QString("%1").arg(Ergebnis2));
emit NewBerichtData("Wert3", QString("%1").arg(Ergebnis3));
...
}
}
Code: Alles auswählen
void BerichtDaten::AddData(QString &Type, QString Value)
{
QMutexLocker Locker(&DataMutex); //Zur Vermeidung, dass diese Funktion ggf. parallel ausgeführt wird (bei mehreren Prozessoren)
QStandardItem *NewItem = new QStandardItem(Value);
//Suchfunktion nach entsprechendem Parent:
QStandardItem *Item;
Item = LastItem; //Ausgangspunkt ist das zuletzt erstellte Item
for(i=0; i<=AnzSpalten; i++)
{
Item=Item->parent(); //Hier tritt dann immer der SIGSEGV auf, weil Item == NULL ist.
}
Item->appendRow(NewItem);
}Dass Daten nicht hinzugefügt werden und somit Spalten fehlen habe ich per Debugausgabe schon nachprüfen können.
Nachtrag
Ich habe jetzt einen funktionierenden Workaround:
Wenn ich die Berichtklasse als Membervariable des Rechenthreads erstelle und in dem Rechenthread die Daten durch direkten Aufruf der Funktion AddData(...) dem Bericht hinzufüge (anstatt die Signal-Slot-Verbindung zu nutzen), dann funktioniert der Spaß offensichtlich. Die Problemquelle ist meiner Meinung nach also auf diese Signal-Slot-Verbindung zurück zu führen, bzw. auf Seiteneffekte die diese Verbindung mit anderen Programmteilen entstehen lässt. Aber hier bin ich überfragt und hoffe auf eure Erfahrung und Anregungen zur Klärung der Problemursache.
Wenn ich die Berichtklasse als Membervariable des Rechenthreads erstelle und in dem Rechenthread die Daten durch direkten Aufruf der Funktion AddData(...) dem Bericht hinzufüge (anstatt die Signal-Slot-Verbindung zu nutzen), dann funktioniert der Spaß offensichtlich. Die Problemquelle ist meiner Meinung nach also auf diese Signal-Slot-Verbindung zurück zu führen, bzw. auf Seiteneffekte die diese Verbindung mit anderen Programmteilen entstehen lässt. Aber hier bin ich überfragt und hoffe auf eure Erfahrung und Anregungen zur Klärung der Problemursache.
haarsträubend, diese Konzeptlosigkeit
Folgende Punkte:
1. Wie du die Daten ("Factor") in den Thread manövrierst ist grauslig (zyklische Abhängigkeit von Thread und GUI) und gefährlich. Denn: angenommen, der Aufruf "ThreadWait->wakeOne();" ist nichtblockierend, kann der Wert "Factor" mehrmals überschrieben werden bis der Thread ihn überhaupt liest. Sollte "wakeOne" blockierend sein, verlierst du bei schnellen Änderungen den Sinn des Threads (weil die GUI dann ja doch wieder warten muss).
2. Wenn du die Resultate direkt (über einen Pointer im Thread) in die Berichtsklasse zurückgibst ist das zwar aus OOP-Sicht bedenklich, aber viel wichtiger ist, dass du die (Model?)Daten gar nicht multithreadingtauglich schützen kannst. Wenn du in "AddDada()" einen Mutex hast ist das ja schön und gut, aber die GUI kann evt. trotzdem noch parallel Lesen (gleichzeitig zum "insertRow()"). Ausserdem können solche Operationen (insertRow()) in einer Model/View-Umgebung zu Signals wie "LayoutChanged()" führen, was wiederum zu Problemen führt (GUI-Operationen im Kontext des Threads).
Soweit so gut...
Das kann man natürlich schon noch ausbauen (auf mehrere double-Werte oder so). Wenn dir das nicht gefällt kannst du schon bei deiner Lösung bleiben.. aber es geht definitiv auch einfacher..
hth..
Folgende Punkte:
1. Wie du die Daten ("Factor") in den Thread manövrierst ist grauslig (zyklische Abhängigkeit von Thread und GUI) und gefährlich. Denn: angenommen, der Aufruf "ThreadWait->wakeOne();" ist nichtblockierend, kann der Wert "Factor" mehrmals überschrieben werden bis der Thread ihn überhaupt liest. Sollte "wakeOne" blockierend sein, verlierst du bei schnellen Änderungen den Sinn des Threads (weil die GUI dann ja doch wieder warten muss).
2. Wenn du die Resultate direkt (über einen Pointer im Thread) in die Berichtsklasse zurückgibst ist das zwar aus OOP-Sicht bedenklich, aber viel wichtiger ist, dass du die (Model?)Daten gar nicht multithreadingtauglich schützen kannst. Wenn du in "AddDada()" einen Mutex hast ist das ja schön und gut, aber die GUI kann evt. trotzdem noch parallel Lesen (gleichzeitig zum "insertRow()"). Ausserdem können solche Operationen (insertRow()) in einer Model/View-Umgebung zu Signals wie "LayoutChanged()" führen, was wiederum zu Problemen führt (GUI-Operationen im Kontext des Threads).
Soweit so gut...
Da hast du irgendwas nicht verstanden... ich mache ein Beispiel:3: Wenn ich ein Signal mit den Daten an den Thread schicke, wird der Slot ja nicht im Rechenthread sondern im Hauptthread ausgeführt. Da müsste ich dann ebenso eine Waitcondition verwenden, um den Thread weiter arbeiten zu lassen und die Variablenzuweisung auch per Mutex schützen. Wäre in meinen Augen der selbe Aufwand.
Code: Alles auswählen
Thread::Thread(...)
{
moveToThread(this); // damit Slots im Thread-Kontext ausgeführt werden
}
Thread::berechne(int v)
{
NeuerWert = v;
StelleBerechnungenAn();
emit NewBerichtData("Wert1", QString("%1").arg(Ergebnis1));
}
Thread::run()
{
exec();
}
GUI::GUI...
{
connect(slider, SIGNAL(valueChanged(int)),
thread, SLOT(berechne(int)));
// Fertig.. jedes "valueChanged(..)" kickt die Berechnung an
thread->start();
}
hth..
Naja, Konzeptlosigkeit hin oder her: Es muss irgendwie schnellstmöglich funktionstüchtig sein... : / (ich mag es auch nicht, aber manchmal hat man keine Wahl)
Zu 1.: Den Kritikpunkt verstehe ich und werde ihn mal anschauen u. ggf. beheben. Ist mir bisher so noch nicht aufgefallen. Aber es ist im großen und ganzen nicht wichtig, ob der Rechenthread jeden kleinsten Sliderschritt mitbekommt oder auch mal ein paar Werte überspringt.
Zu 2.: Die Berichtsachen erscheinen in keiner GUI solange der Thread läuft. Von daher geht mein Workaround. Die Daten werden erst später ausgelesen. Und ein Pointer wäre bei dem Signal-Slot-Prinzip unschön, da man ja nicht weiß, ob die Daten, auf die der Pointer zeigt, noch aktuell sind.
Zu 3.: Die Sache mit dem "moveToThread()" war mir immer etwas suspekt. Zumindest habe ich die Folgen und Abhängigkeiten aus nicht vollständig verstanden beim Lesen der Doku und deswegen lieber die Finger davon gelassen. Außerdem läuft in dem Rechenthread auch noch jede Menge anderer Kram außen rum. Auf Anhieb wüsste ich jetzt nicht wie ich das umbau. Aber beim nächsten Mal muss ich mir das moveToThread() genauer ansehen - scheint ja sehr oft verwendet zu werden (bin einigen Links aus dem Forum gefolgt...)
Um nochmal zum Kern der Sache und meinem obigen Beispiel zu kommen: Das Problem ist, dass beim Bericht manchmal nur der Eintrag "Wert1" mit der Variable1 und anschließend gleich "Wert3" mit der Variable3 ankommt. Der Eintrag "Wert2" mit der Variable2 fehlt jedoch...
Zu 1.: Den Kritikpunkt verstehe ich und werde ihn mal anschauen u. ggf. beheben. Ist mir bisher so noch nicht aufgefallen. Aber es ist im großen und ganzen nicht wichtig, ob der Rechenthread jeden kleinsten Sliderschritt mitbekommt oder auch mal ein paar Werte überspringt.
Zu 2.: Die Berichtsachen erscheinen in keiner GUI solange der Thread läuft. Von daher geht mein Workaround. Die Daten werden erst später ausgelesen. Und ein Pointer wäre bei dem Signal-Slot-Prinzip unschön, da man ja nicht weiß, ob die Daten, auf die der Pointer zeigt, noch aktuell sind.
Zu 3.: Die Sache mit dem "moveToThread()" war mir immer etwas suspekt. Zumindest habe ich die Folgen und Abhängigkeiten aus nicht vollständig verstanden beim Lesen der Doku und deswegen lieber die Finger davon gelassen. Außerdem läuft in dem Rechenthread auch noch jede Menge anderer Kram außen rum. Auf Anhieb wüsste ich jetzt nicht wie ich das umbau. Aber beim nächsten Mal muss ich mir das moveToThread() genauer ansehen - scheint ja sehr oft verwendet zu werden (bin einigen Links aus dem Forum gefolgt...)
Um nochmal zum Kern der Sache und meinem obigen Beispiel zu kommen: Das Problem ist, dass beim Bericht manchmal nur der Eintrag "Wert1" mit der Variable1 und anschließend gleich "Wert3" mit der Variable3 ankommt. Der Eintrag "Wert2" mit der Variable2 fehlt jedoch...