Datenabfrage in separaten QThread

Alles rund um die Programmierung mit Qt
Antworten
chrislo1976
Beiträge: 105
Registriert: 24. Februar 2008 09:45

Datenabfrage in separaten QThread

Beitrag von chrislo1976 »

Hallo zusammen!

Ich bin momentan dabei die Verwendung von Threads zu testen.
Mein Ziel ist es hauptsächlich, das Auslesen von Daten aus einer Datenbank in einen separaten Thread auszulagern, damit die GUI dabei nicht einfriert.
Zusätzlich möchte ich dem Benutzer während dem Auslesen eine Rückmeldung geben wieviel Daten schon gelesen wurden und (optimalerweise) die bereits gelesenen Daten schon anzeigen.

Ich habe mir ein kleines Testprojekt erstellt in dem ich diese Funktionalität testen kann. Statt einer Datenbank ist einfach eine Schleife enthalten, die Dummy-Daten erstellt. Diese Dummydaten werden in eine QStringList eingefügt und diese Liste wird regelmäßig zum Hauptthread geschickt und dort in einer einzigen Liste zusammengefügt.

Prinzipiell funktioniert das auch so wie ich es will, allerdings dauert es nach einem Auslesevorgang recht lange bis ein neuer Auslesevorgang gestartet werden kann, oder auch wenn das Programm beendet werden soll. Ich vermute dass das mit dem "Aufräumen" der QStrings durch Qt zusammenhängt.

Hat jemanden einen Tipp für mich um das zu verbessern, oder allgemein ein paar Hinweise ob diese Herangehensweise sinnvoll ist oder etwaige Gefahren birgt?!

Danke schon mal!

Gruß,
Christian
Dateianhänge
threadTest.zip
Testprojekt
(3.96 KiB) 175-mal heruntergeladen
pfid
Beiträge: 535
Registriert: 22. Februar 2008 16:59

Beitrag von pfid »

Ich bin mir nicht sicher, aber ich könnte mir vorstellen, dass moveToThread nur das tut, was du willst, wenn die Nebenläufigkeit schon da ist (nach dem start()-Call).

Abgesehn davon nennst du einen Member 'dataThread' (vom Typ QThread), dann aber eine Klasse 'DataThread', deren Member dann 'dataProvider' heiß. Bist du sicher, dass das irgendjemand irgendwann wieder verstehen und interpretieren kann? ;)
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

pfid hat geschrieben:Ich bin mir nicht sicher, aber ich könnte mir vorstellen, dass moveToThread nur das tut, was du willst, wenn die Nebenläufigkeit schon da ist (nach dem start()-Call).
moveToThread() macht genau eines: Das Eventhandling in den angegebenen Thread schieben.
Setz mal das moveToThread() VOR die connections auf deinen dataProvider, NACH den connects startest du den Thread. Desweiteren sollte das QApplication::processEVents() unnötig sein. Der SLOT liegt in einem anderen Thread und wird von der Eventloop dort abgefangen.

Ich kann hier auch kein Problem mit langer Pause feststellen.
chrislo1976
Beiträge: 105
Registriert: 24. Februar 2008 09:45

Beitrag von chrislo1976 »

Hallo!

Danke für eure Hinweise!

Die Namensgebung ist sicherlich nicht optimal, aber es ist wie gesagt ein Testprojekt...

Also, so wie der Code jetzt ist friert die GUI schon nicht ein; also so wie ich es möchte.
Nachdem ich das moveToThread jetzt vor die Connects verschoben habe, ändert sich daran eigentlich nichts (oder ich hab's nur nicht bemerkt)

Das processEvents benötige ich (zumindest hab ich es nicht ohne geschafft) damit das Abbrechen während dem Auslesen funktioniert.
Wenn ich das weglasse läuft die Schleife immer komplett durch.

Wegen der langen Pause:
Probiert bitte mal gleich nach der MessageBox nochmals den Startbutton zu drücken. Dann dauerts ne Weile bis der Zähler wieder zu laufen anfängt. Das verzögerte Beenden merke ich innerhalb VisualStudio, dort ist das Fenster schon weg, die Anwendung wird aber noch als laufend angezeigt.

Seht ihr denn in dem Code allgemein Probleme, oder könnte man das schon so machen?

Gruß,
Christian
franzf
Beiträge: 3114
Registriert: 31. Mai 2006 11:15

Beitrag von franzf »

chrislo1976 hat geschrieben:Das processEvents benötige ich (zumindest hab ich es nicht ohne geschafft) damit das Abbrechen während dem Auslesen funktioniert.
Wenn ich das weglasse läuft die Schleife immer komplett durch.
Das stimmt natürlich. Wenn du es weiterhin so machen willst, frag vllt. vorher nach ob events anliegen (hasPendingEvents()).
Du kannst aber den Stop direkt im Hauptthread triggern ohne Umweg über Events. Dann setzt der Hauptthread das break-Flag. MMn. brauchst du hier keine Synchronisierung, da die Daten nicht lebensnotwendig sind - dann gibts halt eine Schleife mehr, das Programm läuft nicht Gefahr, undefiniertes Verhalten zu erzeugen... Wenn du ganz sicher sein willst, bau dir eine Lösung mit QAtomicInt.
Wegen der langen Pause:
Probiert bitte mal gleich nach der MessageBox nochmals den Startbutton zu drücken. Dann dauerts ne Weile bis der Zähler wieder zu laufen anfängt. Das verzögerte Beenden merke ich innerhalb VisualStudio, dort ist das Fenster schon weg, die Anwendung wird aber noch als laufend angezeigt.
Kannst du über die Zeit mal genauere Angaben machen? Ich kann hier auf nem AMD64 3700+ dein geschildertes Verhalten nicht reproduzieren.
solarix
Beiträge: 1133
Registriert: 7. Juni 2007 19:25

Beitrag von solarix »

Folgendes Feedback:

1. Sieht prinzipiell alles ganz gut aus!

2. "moveToThread()" vor/nach start() oder vor/nach connect() macht definitiv keinen Unterschied, habe ich auch schon feststellen können.

3. du hast noch ein Memoryleak wenn die QStringList allokiert aber nicht als Signal versendet wird (in den else-Zweig bei "if (!sl->isEmpty())" gehört noch ein "delete")

4. Bei mir (Ubuntu) fühlt sich das Programm ganz genau so an, wie es sollte. Bei 250000 komme ich gar nicht nach mit Klicken :wink:
Wenn ich den count erhöhe, kann ich sauber abbrechen und gleich wieder starten (ganz sicher innerhalb des 250ms-Taktes).
Ich schätze, dass in deiner Umgebung evt. "processEvents()" etwas langsamer ist. Du kannst das ja gut mit "qDebug()"-Ausgaben oder mit dem Debugger (einfach nach dem "Stopp"-Klick das Programm anhalten) prüfen, ob sich der Thread noch darin befindet..

5. Beim Programm-Abbruch mit laufendem Thread würde ich den User informieren, dass der aktuelle Job abgebrochen wird und synchron runterfahren. Also in der GUI so in etwa:

Code: Alles auswählen

{
   // evt. dem Job, mitteilen, dass er sich beenden soll..
   ...
   // danach:
   job->deleteLater();
   connect(job, SIGNAL(destroyed(QObject*), thread, SLOT(quit()));
   connect(thread, SIGNAL(finished(), thread, SLOT(deleteLater()));

   // QSplashScreen oder sonst was fuer den User:
   ..

   thread->wait(...);
}
6. Bei diesem Konzept (QObject mit moveToThread()) verzichtet man häufig komplett auf einen eigenen Loop, sondern erstellt den Loop mittels Queued-Signals:

Code: Alles auswählen

DataJob:DataJob() ...
{
  connect(this, SIGNAL(nextJob), this, SLOT(job()), Qt::QueuedConnection);
}

void ...:job()
{
  if (m_break)
    return;

   // erarbeite eine neue QStringList
  ..
.
  emit newLine(sl);
  if (es_gibt_noch_mehr_zu_tun)
    emit nextJob();
}
Jetzt entsteht der Loop durch den Eventloop des QThreads und du brauchst weder einen eigenen "while" noch "processEvents()".

7. Datenbanken und Multithreads:
achte darauf, dass du wirklich eine eigene Connection im Thread erstellst.. Gib der Datenbankverbindung einen garantiert einmaligen Namen (2. Argument bei http://doc.qt.nokia.com/latest/qsqldata ... ddDatabase z.B. mit der Thread-ID)
Wenn du das nicht tust, ersetzt der Thread möglichweise eine (in einem anderen Thread) in Action befindliche Connection....

[EDIT]
punkt 7 hinzugefügt

[EDIT2]
Desweiteren sollte das QApplication::processEVents() unnötig sein. Der SLOT liegt in einem anderen Thread und wird von der Eventloop dort abgefangen.
Das zeigt, dass auch Profis hin und wieder den Überblick verlieren :wink:
Natürlich wird in diesem Beispiel mit diesem Loop "processEvents()" benötigt.. denn das QObject gehört ja eben durch "moveToThread" zum Thread.. und der kann nicht gleichzeitig im while-Loop sein UND den Slot ausführen...


hth!
chrislo1976
Beiträge: 105
Registriert: 24. Februar 2008 09:45

Beitrag von chrislo1976 »

Hallo zusammen!

Ich hab das Programm jetzt mal ausserhalb von VisualStudio gestartet, und siehe da, diese Verzögerungen treten hier nicht auf! Es läuft so schnell wie von euch beschrieben.
Komisch, da ich sonst immer alles direkt innerhalb von VisualStudio teste und mir noch nie sowas aufgefallen ist...

@franzf:
Das mit dem hasPendingEvents() ist eine gute Idee, werde ich einbauen.
Die Verzögerung innerhalb VS beträgt gut 5sec...

@solarix:
1) Das freut mich! :)
2) Gut, dann hab ich mich nicht getäuscht.
3) Oops, da hast du natürlich recht!
4) Ausserhalb VS ist es jetzt bei mir auch so.
5) Würde ich in einem richtigen Programm auch so machen, allerdings würde ich einen "Daten-Hol-Thread" machen, der die ganze Zeit über läuft und bei Bedarf die Daten liefert. Wenn sehr häufig Daten geladen werden müssen wäre die Thread-Erstellerei/Starterei ein zusätzlicher Aufwand.
6) Hat auch was! Allerdings bin ich mir (jetzt noch) nicht sicher, wie ich das mit SQL-Abfragen bewerkstelligen kann. Ich fürchte das könnte den Ablauf etwas verkomplizieren. Aber ich werde das mal ausprobieren!


Hat eigentlich jemand Bauchschmerzen dabei den Speicher in den einen Thread zu reservieren, den Zeiger darauf über Events zu versenden und dann in einem anderen Thread den Speicher wieder zu löschen?
Könnte es evtl. sein dass Events verloren gehen? Dann würden ja Speicherlecks enstehen...

Gruß,
Christian
Antworten