QThread: Terminate?



  • Okay, ich habe den Thread Mal durchgelesen.

    Aber die Begründung dafür, dass man von QThread nicht ableiten soll, verstehe ich nicht. Letztlich habe ich am Ende nur eine Klasse mehr, wenn ich einen Worker baue. Also aus "QThread liefert alles, inkl. Signals, was ich brauche, um mit einem Thread zu arbeiten" impliziert für mich nicht "also sollte man davon nicht ableiten und ein eigenes Objekt bauen".

    Kann mir da jemand helfen?



  • Es geht um das Abarbeiten der Events, die bei der Kommunikation zwischen verschiedenen Threads per SIGNAL/SLOT anfallen. SLOTS werden immer in dem Thread abgearbeitet, in dem das Objekt erzeugt wurde. Das Thread-Objekt liegt üblicherweise in einem anderen THread als in sich selbst (logisch, wie sollte das gehen), damit würden IMMER alle SLOTS in einem anderen Thread als dem, der die SLOTS hält, ausgeführt, was wieder zu Performanceproblemen führt, weshalb man überhaupt erst den neuen Thread aufgemacht hat. Um das zu verhindern, muss man explizit das Objekt in eine andere Eventloop schieben, was in einem unschönen (und für viele nicht nachvollziehbaren) "this->moveToThread(this);" im Konstruktor des Threads führt. Seit Qt-irgendwas ist QThrad::run() nichtmehr pure virtual, sondern macht ein "exec()". Leider steht das run() implementieren noch in der Doku, und viele kennen es halt von Java her.

    Deshalb:
    * Will der Thread per SIGNAL/SLOT kommunizieren, einfach von QObject ableiten, in einen Thread schieben und Thread starten.
    * Führt der Thread einmalig irgend eine Funktion aus, nimm doch gleich QtConcurrent::run().



  • Danke für die ausführliche Erklärung!

    Leider habe ich es immer noch nicht ganz verstanden. Also wenn ich in meinem Hauptthread HT einen neuen Thread T1 eröffne und zwar, indem ich einfach nur QThread nutze, dann werden davon ausgesandte Signals und damit auch die Slot-Ausführungen von T1 gefahren.

    Wenn ich also in einer Klasse, die im HT laufen soll, einen Slot bastele, der auf ein Signal von QThread reagiert, wird eben dieser Slot von T1 ausgeführt.

    Wenn ich jetzt ein QObject erstelle und das in den Thread schiebe und die Signals auf die Slots von diesem QObject verbinde, dann laufen diese Slots/Signals aber doch auch in T1? Und wenn ich das QObject nicht in T1 schiebe, dann doch auch. Also ich verstehe nicht so ganz, was dieses Schieben genau bewirkt. Auch sehe ich leider nicht die Performanceprobleme durch die Signale. 😕

    Sorry, ist kompliziert für mich, würde mich über weitere Erklärung freuen.



  • 😉

    Wenn ich also in einer Klasse, die im HT laufen soll, einen Slot bastele, der auf ein Signal von QThread reagiert, wird eben dieser Slot von T1 ausgeführt.

    Das Objekt O1 liegt im HT. Egal aus welchem Thread jetzt ein SIGNAL kommt, der SLOT des O1 wird IMMER im HT ausgeführt - außer es wurde explizit per "O1->moveToThread(T1);" in die EventLoop des anderen Threads verschoben.
    Und das ist auch das angesprochene Problem.
    Kurzes Beispiel (geht mir nicht um Kompilierbarkeit, sondern ums Prinzip)

    class Thread : public QThread {
      Q_OBJECT
    public:
      Thread();
    
    public slots:
      void doSomething()
    
    private:
      virtual void run();
    };
    
    //// Verwendung:
    // Wir sind im Haupt-Thread
    Thread *t1 = new Thread;
    // t1 liegt im Hauptthread. t1->doSomething wird deshalb IMMER im HT ausgeführt!
    // Um das zu verhindern, kommt so ein Konstruktor raus:
    Thread::Thread() {
      this->moveToThread(this);
    }
    

    Weil auch noch run implementiert wird - egal warum, meistens wird es ja gar nicht mehr gebraucht, weil wir über SIGNAL/SLOT kommunizieren, und das Setup in run() auch im Konstruktor erledigt werden könnte - und am Ende exec() vergessen, klappen gar keine SIGNAL/SLOTS mehr 😉



  • Sorry für die späte Nachfrage, wollte erst noch etwas rumprobieren. Also ich glaube, ich hab's jetzt endlich kapiert. Ich dachte, ich hätte etwas an der generellen Thread-Logik nicht verstanden, aber dass der Slot in O1 vom HT aufgerufen wird, liegt eben an der Signal/Slot-Implementierung, die anscheinend eine Queue von Signal-Aufrufen im HT hat. Wenn ich also ein Signal feuere, wird in der Queue des Threads, dessen Slot das ist, etwas abgelegt; und der Aufruf der Slots geschieht denn beim Polling in der Eventloop des Threads, unter dem das Objekt mit dem Slot erstellt/registriert wurde. So weit korrekt?

    Und zum Verschieben des Objekts in den Thread: Das macht eigentlich ja nur im Bezug auf die Slots Sinn, die im Objekt definiert sind? Ich habe nämlich bis auf quit und process (was ja sowieso im neuen Thread ausgeführt würde) keine genutzten Slots. Nur hätte ich welche, wäre das natürlich problematisch. Darum geht's, richtig?

    Falls das alles stimmt, hätt ich aber noch eine Frage:
    Ich habe jetzt einen Workerthread gebastelt, der also bis zum Programmende laufen soll. Um das zu Erreichen habe ich nur einige der Signal/Slots aus dem Tutorial eingebaut. Meine QObject-Klasse hat in process eine Endlosschleife und wird einfach über RRID gelöscht, d.h. ich hab sie einfach auf dem Stack des Hauptfenster angelegt. Wird die QObject-Klasse gelöscht, wird quit des QThread-Objekts aufgerufen und beim finished() vom QThread dann ein deleteLater.

    Funktioniert das denn so? Oder muss ich davon ausgehen, dass das nicht ausreicht?



  • Keiner sonst ne Ahnung?

    Also ich habe folgende etwas wirre Ver-Connect-ung:

    QThread* thread = new QThread();
    workerObject.moveToThread(thread);
    QObject::connect(thread, SIGNAL(started()), &workerObject, SLOT(process()));
    QObject::connect(&workerObject, SIGNAL(finished()), thread, SLOT(quit()));
    QObject::connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));

    workerObject liegt auf dem Stack, wird dann also automatisch beendet. In process befindet sich nämlich eine Endlosschleife, sodass finish erst im dtor emittiert wird. Aber ich glaube, wenn erst nach Zerstörung vom workerObject das Signal kommt, ist der signal/slot-Mechanismus möglicherweise bereits beendet bzw. der Zyklus wird nich nochmal durchlaufen? Und selbst wenn, dann wird thread gequitted aber das finished-Signal evtl. nicht mehr weitergeleitet, sodass deleteLater wiederum nicht aufgerufen wird.

    Kurzum: Sauber aufgeräumt wird vermutlich nicht.

    Ich könnte statt der Endlosschleife ein abort-Flag einfügen, das im dtor meiner Hauptthread-Klasse gesetzt wird. Doch auch dann würden diese Signals alle erst im dtor gefeuert.

    Wie räumt man solche Threads in QT sauber auf?



  • Aus C++-Sicht sollte ein Destruktor einfach das Objekt zerstören. Ein emit läuft dem irgendwie dagegen, vor allem wenn auf die Existenz des gerade zerstörten Objekts Wert gelegt wird.
    Warum kannst du das Signal nicht am Ende von process emittieren?

    Ansonsten:
    workerObject dynamisch anlegen, bei Bedarf mit deleteLater() zerstören, dann bleibt es auch in der Queue, bis alle angeschlossenen connections abgearbeitet sind.



  • Ja, normalerweise würde man einfach den Destruktor das machen lassen, aber das wird durch QT hier ja so nicht gelöst...

    Das Problem ist ja, dass mein Thread permanent läuft. Der soll nicht eine Aufgabe abarbeiten und zu Ende sein, sondern der wartet brav auf Aufgaben; ein Worker-Thread halt.

    Daher gibt es das Ende von "Process" nicht. Ich kann aborten und dann emittiert er das Signal, aber das dauert alles zu lange und der läuft nicht mehr durch die Eventloop.

    Die Frage ist, ob QT nicht sowieso alles aufräumt, sobald es in einer Eventloop steckt, sodass ich mir keine Sorgen machen muss, ob Speicher nicht freigegeben wird? Ich glaube nämlich mittlerweile, dass sich das so nicht wirklich lösen lässt. Ich kann ja auch nicht den Thread einfach am Ende des Programms zerstören, denn wenn der gerade noch läuft, darf ich kein delete auf QThread machen. Und wenn ich dem Thread ein abort schicke, kann es ja sein, dass er noch etwas braucht, um zu terminieren... daher ja auch diese Signal/Slot-Verbindungen.



  • Ist das ein Programm mit Hauptfenster? Wenn ja, dort das closeEvent() implementieren, und das event->ignore()ieren, wenn der thread noch läuft. Am besten dem User ne MessageBox o.Ä. zeigen, dass jetzt abgebrochen wird, und evtl. per timer pollen, bis der Thread beendet ist.

    Aber generell: Wenn der Prozess beendet ist, räumt das OS selber auf (war früher nicht so). Probleme mit Resourcen sollte es deshalb nicht geben.



  • Ja, ist ein Programm mit Hauptfenster. Also ich würde den Benutzer eben nur sehr ungerne mit einer Messagebox oder einem Delay vor Programmende stören. Kann ich davon ausgehen, dass nach Programmende das Aufräumen automatisch erfolgt?

    Ich meine, ich finde es einfach schlechten Stil nicht vernünftig aufzuräumen (im Code, in meinem Zimmer genieße ich gesundes Chaos). Aber diese Threads sind durchaus ein Problem für sich. Wie macht man das denn üblicherweise? Habe bei Google auch nichts zu Worker-Thread-Cleanup (o.ä.) gefunden. 😕

    Edit: Falls wer googlet, hier Mal der delayed-close-Code. Mit sleep und wait war nämlich leider nicht viel zu wollen.

    void MainWindow::closeEvent( QCloseEvent * event )
    {
    	workerThread.Abort();
    
    	QTime timer;
    	timer.start();
    
    	while(timer.elapsed() < 400)
    		QCoreApplication::processEvents(QEventLoop::DeferredDeletion, 400);
    
    	event->accept();
    }
    

Anmelden zum Antworten