[gelöst] qApp->processEvents() aus Thread ausführen

Alles rund um die Programmierung mit Qt
Antworten
N¤X
Beiträge: 77
Registriert: 21. September 2009 12:24

[gelöst] qApp->processEvents() aus Thread ausführen

Beitrag von N¤X »

Hallo,
ich habe in nem Programm ne Ausgabefunktion die einfach Warnungen und Fehlermeldungen in ein TableModel schreibt. Damit ich die Ausgabe auch schon während einer länger laufenden Funktion seh ruf ich darin schön qApp->processEvents() auf, was auch gut funktioniert. Ich mach das so und nicht mit einem extra Worker-Thread, da man solange die Funktion läuft eh nicht mit dem Programm interagieren soll.

Jetzt hab ich aber einen Teil der Funktion mit QtConcurrent::run parallelisiert, und die Ausgabe-Aufrufe aus diesem parallelisierten Teil werden eben nicht direkt angezeigt, sondern erst wenn alle runs abgearbeitet sind (Die Funktion wartet am Ende mit QThreadPool::globalInstance()->waitForDone(); auf die runs).

Funktioniert qApp->processEvents() aus einem Thread heraus nicht? Oder was läuft da schief? Ich hab jetzt schon ne Weile rumgesucht aber nix gefunden. Ich bräuchte halt ne Lösung die sowohl bei einem seriellen Aufruf als auch bei einem Aufruf aus einem Thread heraus funktioniert.

Hier mal die Ausgabe-Funktion

Code: Alles auswählen

void MainWindow::messageHandler(QtMsgType type, const char *msg) {
   mutex->lock();
   logModel->addEntry(int(type), QString(msg));
   qApp->processEvents();
   mutex->unlock();
}
Zuletzt geändert von N¤X am 3. Februar 2010 13:03, insgesamt 1-mal geändert.
mfg N¤X
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Hi! Schlechtes Design, schlechte Idee... Beachte folgende Punkte:

0. processEvents() aus dem Kontext des Threads aufzurufen bedeutet, dass auch die resultierenden GUI-Updates in diesem Kontext ausgeführt werden. Und das ist verboten/nicht möglich.
1. Die meisten Qt-Methoden sind reentrant, aber nicht threadsafe. Lies das nach (google), wenn du da nicht sattelfest bist.
2. Entkopple den Thread soweit wie möglich von anderen Instanzen wie z.B. der GUI. Ein (queued-)Signal ist besser geeignet, als eine Methode (Static oder mit zyklischen Abhängigkeiten) aufzurufen.
3. Für Log-Funktionen wird häufig das Singleton-Pattern (zentrale "Registrierstelle") für Log-Meldungen eingesetzt.. google danach, falls du das nicht kennst..

Eine relativ einfache Lösung bei dir wäre z.B., ein Slot des Models mit einem Signal des Threads zu verbinden (connect() in der Kontroller-Klasse welche beide Instazen kennt, z.B. GUI). Damit wäre das Problem entkoppelt, beide Instanzen kennen sich nicht und durch die Qt::QueuedConnection stimmt auch der Threadkontext, wenn das Model die Daten erhält und die Signals (layoutChanged() usw.) feuert...


hth..

EDIT: Punkt 0 nachgetragen.
N¤X
Beiträge: 77
Registriert: 21. September 2009 12:24

Beitrag von N¤X »

solarix hat geschrieben:Hi! Schlechtes Design, schlechte Idee...
-_-"
solarix hat geschrieben:0. processEvents() aus dem Kontext des Threads aufzurufen bedeutet, dass auch die resultierenden GUI-Updates in diesem Kontext ausgeführt werden. Und das ist verboten/nicht möglich.
OK :/
solarix hat geschrieben:1. Die meisten Qt-Methoden sind reentrant, aber nicht threadsafe. Lies das nach (google), wenn du da nicht sattelfest bist.
Sowas hab ich auch nachgelesen, deshalb hab ich den Mutex drin, oder was meinst du?
solarix hat geschrieben:2. Entkopple den Thread soweit wie möglich von anderen Instanzen wie z.B. der GUI. Ein (queued-)Signal ist besser geeignet, als eine Methode (Static oder mit zyklischen Abhängigkeiten) aufzurufen.
Welchen Thread? Ich hab nur das QtConcurrent::run das mir Threads macht, und ich weiß nicht, inwiefern ich das weiter entkoppeln soll...
solarix hat geschrieben:3. Für Log-Funktionen wird häufig das Singleton-Pattern (zentrale "Registrierstelle") für Log-Meldungen eingesetzt.. google danach, falls du das nicht kennst..
Darüber bin ich in der Tat schonmal gestolpert, aber ich wollte keine große Sache draus machen mit eigener Klasse und Gedönz. Die Funktion die ich gepostet hab hab ich einfach als MessageHandler registriert, darüber bin ich beim Informieren auch gestolpert und es macht genau was ich will: Ohne große Bekanntmachungen kann ich so von Überall aus einfach Warnings und Fehlermeldungen werfen und der Message Handler schnappt die sich einfach und unkompliziert, und ich hab ein Errorlevel gratis dazu, was ich auch brauche, also wenn alles nur Sequentiell wäre wärs echt perfekt...
solarix hat geschrieben:Eine relativ einfache Lösung bei dir wäre z.B., ein Slot des Models mit einem Signal des Threads zu verbinden (connect() in der Kontroller-Klasse welche beide Instazen kennt, z.B. GUI). Damit wäre das Problem entkoppelt, beide Instanzen kennen sich nicht und durch die Qt::QueuedConnection stimmt auch der Threadkontext, wenn das Model die Daten erhält und die Signals (layoutChanged() usw.) feuert...
Wie gesagt, dank QtConcurrent::run weiß ich nicht, wie ich an den Thread irgendwelche Slots connecten soll, und da der Thread gerne mal ne Million mal generiert wird weiß ich nicht, ob das nicht noch mehr overhead fabriziert.
Außerdem will ich den Fehleroutput des gesamten Programms sammeln und wollte eigentlich vermeiden eine extra Klasse zu machen die ich überall hin connecten oder überall bekannt machen muss.

Danke für die schnelle Hilfe. Wenn du irgendwas weißt wie ichs doch noch mit nem MessageHandler hinkrieg wär das super, ansonsten wärs auch ok wenn man irgendwie in ner funktion rausfinden könnte, ob die jetzt sequenziell oder parallel ausgeführt wird, damit ich halt nur bei sequenziell Output in Echtzeit hab. Bevor ich mich noch mit Singletons rumärger lass ichs lieber wies ist und mach das processEvents einfach komplett raus.
mfg N¤X
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Sowas hab ich auch nachgelesen, deshalb hab ich den Mutex drin, oder was meinst du?
Mit Semaphoren/Mutexen den Zugriff zu serialisieren ist grundsätzlich zwar richtig.. aber dann muss das von allen Seiten her geschehen und weil die Ressource in diesem Fall nicht von dir ist, hast du darauf keinen Einfluss...

Egal.. ich habe folgendes gelesen (http://doc.trolltech.com/4.6/qcoreappli ... cessEvents):
Calling this function processes events only for the calling thread.
Note: This function is thread-safe.
In diesem Fall ist Punkt 1 nicht relevant..
Darüber bin ich in der Tat schonmal gestolpert, aber ich wollte keine große Sache draus machen mit eigener Klasse und Gedönz.
Eine Klasse mit 4-5 Elementen ist keine grosse Sache :wink: .. ausserdem lohnt sich ein gutes Design auch in kleinen Projektchen..
Wenn du irgendwas weißt wie ichs doch noch mit nem MessageHandler hinkrieg
wenn du dabei bleiben willst, würde ich beim LogModel ansetzen. Ich würde in "addEntry(..)" die neue Meldung in eine Art "ToDo"-list (QList) werfen. Der Zugriff auf diese Liste muss dann eben Mutex-geschützt erfolgen.
Der Handler sieht dann nur noch wie folgt aus:

Code: Alles auswählen

void MainWindow::messageHandler(QtMsgType type, const char *msg) {
   logModel->addEntry(int(type), QString(msg));
}
Wenn das Model so also neue Meldungen sammelt, muss es sich noch im GUI-Thread darum kümmern. Also entweder muss es periodisch (QTimer) den Stapel prüfen und den tatsächlichen Model-Daten hinzufüegen oder das Model sendet in "addEntry()" sich selbst ein Signal "neue_Daten_sind_hier"..

hth...
[/code]
AuE
Beiträge: 918
Registriert: 5. August 2008 10:58

Beitrag von AuE »

Was is denn am Qt-installMsgHandler in der Main GUI falsch? Ich weiss nicht ob er die Sachen aus den Threads kriegt aber mit plugins klappts tadellos
N¤X
Beiträge: 77
Registriert: 21. September 2009 12:24

Beitrag von N¤X »

solarix hat geschrieben:wenn du dabei bleiben willst, würde ich beim LogModel ansetzen. Ich würde in "addEntry(..)" die neue Meldung in eine Art "ToDo"-list (QList) werfen.
Dann soll ich also die Messages in ne Liste einfügen, um sie dann später von der Liste in die eigentliche Liste zu verschieben? Ich weiß nicht, ob das so viel Sinn macht...
Ich hab die echtzeit-Ausgabe jetzt halt weggelassen, wenn irgendwas ne Message wirf und will dass die auch gleich angezeigt wird soll es sich gefälligst selber drum kümmern. Das ganze sieht jetzt so aus und funktioniert wie ich das will:

Code: Alles auswählen

void MainWindow::messageHandler(QtMsgType type, const char *msg) {
   logModel->addEntry(int(type), QString(msg));
   logView->verticalScrollBar()->setValue(0);
}

Code: Alles auswählen

void LogModel::addEntry(int level, QString message) {
   LogModel::Item newItem;
   newItem.timestamp = QTime::currentTime();
   newItem.loglevel = level;
   newItem.message = message;
   mutex->lock();
   beginInsertRows(QModelIndex(), 0, 0);
   dataList.prepend(newItem);
   endInsertRows();
   mutex->unlock();
}
Danke für die Hilfe :)
mfg N¤X
Antworten