Was ist der richtige Weg, um ein QThread zu implementieren … (Beispiel bitte …)

Die Qt-Dokumentation für QThread sagt, eine class aus QThread zu erstellen und die run-Methode zu implementieren.

Im Folgenden finden Sie die 4.7 Qthread-Dokumentation …

Um eigene Threads zu erstellen, müssen Sie QThread ableiten und run () neu implementieren. Beispielsweise:

class MyThread : public QThread { public: void run(); }; void MyThread::run() { QTcpSocket socket; // connect QTcpSocket's signals somewhere meaningful ... socket.connectToHost(hostName, portNumber); exec(); } 

In jedem einzelnen Thread, den ich erstellt habe, habe ich genau das getan und für die meisten Dinge funktioniert es gut (ich implementiere moveToThread (this) in keinem meiner Objekte und es funktioniert großartig).

Ich habe letzte Woche einen Fehler gemacht (es ist mir gelungen, durchzukommen, wo ich meine Objekte erstellt habe) und habe den folgenden Blogpost gefunden . Hier ist im Grunde gesagt, dass Unterklassen QThread wirklich nicht der richtige Weg ist (und dass die Dokumentation falsch ist).

Dies kommt von einem Qt-Entwickler, also war ich auf den ersten Blick interessiert und nach weiteren Überlegungen, stimme ihm zu. Gemäß den OO-Prinzipien möchten Sie wirklich nur eine class ableiten, um diese class weiter zu verbessern … und nicht nur die classnmethoden direkt zu verwenden … deshalb instanziieren Sie …

Nehmen wir an, ich wollte eine benutzerdefinierte QObject-class in einen Thread verschieben … was wäre der ‘richtige’ Weg, dies zu tun? In diesem Blogpost sagt er, dass er irgendwo ein Beispiel hat … aber wenn jemand es mir weiter erklären könnte, würde es sehr geschätzt werden!

Aktualisieren:

Da diese Frage so viel Aufmerksamkeit auf sich zieht, ist hier ein Kopieren und Einfügen der 4.8 Dokumentation mit der “richtigen” Methode zur Implementierung eines QThreads.

 class Worker : public QObject { Q_OBJECT QThread workerThread; public slots: void doWork(const QString &parameter) { // ... emit resultReady(result); } signals: void resultReady(const QString &result); }; class Controller : public QObject { Q_OBJECT QThread workerThread; public: Controller() { Worker *worker = new Worker; worker->moveToThread(&workerThread); connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater())); connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString))); connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString))); workerThread.start(); } ~Controller() { workerThread.quit(); workerThread.wait(); } public slots: void handleResults(const QString &); signals: void operate(const QString &); }; 

Ich glaube immer noch, dass es sich lohnt, darauf hinzuweisen, dass sie ein zusätzliches Worker::workerThread Mitglied enthalten, das unnötig ist und in ihrem Beispiel nie verwendet wird. Entfernen Sie dieses Stück und es ist ein gutes Beispiel dafür, wie Sie Threads in Qt tun können.

Das Einzige, was ich hinzufügen kann, ist, dass QObject s eine Affinität zu einem einzelnen Thread hat. Dies ist normalerweise der Thread, der das QObject . Wenn Sie also ein QObject im QObject der App erstellen und es in einem anderen Thread verwenden moveToThread() , müssen Sie moveToThread() , um die Affinität zu ändern.

Dadurch QThread Unterklasse von QThread und das Erstellen Ihrer Objekte in der run() -Methode, wodurch Ihre Objekte schön gekapselt QThread .

Dieser Blogeintrag enthält einen Link zu einem Beispiel . Es ist ziemlich kurz, aber es zeigt die Grundidee. Erstellen Sie Ihr QObject s, verbinden Sie Ihre Signale, erstellen Sie QThread , verschieben Sie Ihre QObjects in den QThread und starten Sie den Thread. Die Signal / Slot-Mechanismen stellen sicher, dass die Threadgrenzen korrekt und sicher gekreuzt werden.

Möglicherweise müssen Sie die Synchronisierung einführen, wenn Sie Methoden für Ihr Objekt außerhalb dieses Mechanismus aufrufen müssen.

Ich weiß, dass Qt noch ein paar nette Threading-Möglichkeiten jenseits von Threads hat, mit denen man sich wahrscheinlich vertraut machen sollte, aber ich muss es noch tun 🙂

Hier ist ein Beispiel, wie man QThread korrekt verwendet , aber es hat einige Probleme damit, die sich in den Kommentaren widerspiegeln. Insbesondere, da die Reihenfolge, in der die Schlitze ausgeführt werden, nicht genau definiert ist, könnte dies zu verschiedenen Problemen führen. Der Kommentar vom 6. August 2013 gibt eine gute Idee, wie mit diesem Problem umgegangen werden soll. Ich verwende so etwas in meinem Programm, und hier ist ein Beispielcode, um es zu verdeutlichen.

Die Grundidee ist die gleiche: Ich erstelle eine QThread-Instanz, die in meinem Hauptthread lebt, eine Worker-classninstanz, die in dem neuen Thread lebt, den ich erstellt habe, und verbinde dann alle Signale.

 void ChildProcesses::start() { QThread *childrenWatcherThread = new QThread(); ChildrenWatcher *childrenWatcher = new ChildrenWatcher(); childrenWatcher->moveToThread(childrenWatcherThread); // These three signals carry the "outcome" of the worker job. connect(childrenWatcher, SIGNAL(exited(int, int)), SLOT(onChildExited(int, int))); connect(childrenWatcher, SIGNAL(signalled(int, int)), SLOT(onChildSignalled(int, int))); connect(childrenWatcher, SIGNAL(stateChanged(int)), SLOT(onChildStateChanged(int))); // Make the watcher watch when the thread starts: connect(childrenWatcherThread, SIGNAL(started()), childrenWatcher, SLOT(watch())); // Make the watcher set its 'stop' flag when we're done. // This is performed while the watch() method is still running, // so we need to execute it concurrently from this thread, // hence the Qt::DirectConnection. The stop() method is thread-safe // (uses a mutex to set the flag). connect(this, SIGNAL(stopped()), childrenWatcher, SLOT(stop()), Qt::DirectConnection); // Make the thread quit when the watcher self-destructs: connect(childrenWatcher, SIGNAL(destroyed()), childrenWatcherThread, SLOT(quit())); // Make the thread self-destruct when it finishes, // or rather, make the main thread delete it: connect(childrenWatcherThread, SIGNAL(finished()), childrenWatcherThread, SLOT(deleteLater())); childrenWatcherThread->start(); } 

Etwas Hintergrund:

Die ChildProcesses-class ist ein untergeordneter processmanager, der neue untergeordnete processe mit spawn () – Aufrufen startet, die Liste der aktuell laufenden processe beibehält und so weiter. Es muss jedoch die untergeordneten Status verfolgen, was bedeutet, dass der Aufruf waitpid () unter Linux oder WaitForMultipleObjects unter Windows verwendet wird. Früher habe ich diese im nicht-blockierenden Modus mit einem Timer angerufen, aber jetzt möchte ich eine schnellere Reaktion, was Blocking-Modus bedeutet. Hier kommt der Thread ins Spiel.

Die ChildrenWatcher-class ist wie folgt definiert:

 class ChildrenWatcher: public QObject { Q_OBJECT private: QMutex mutex; bool stopped; bool isStopped(); public: ChildrenWatcher(); public slots: /// This is the method which runs in the thread. void watch(); /// Sets the stop flag. void stop(); signals: /// A child process exited normally. void exited(int ospid, int code); /// A child process crashed (Unix only). void signalled(int ospid, int signal); /// Something happened to a child (Unix only). void stateChanged(int ospid); }; 

Hier, wie es funktioniert. Wenn all diese Dinge gestartet sind, wird die ChildProcess :: start () -Methode aufgerufen (siehe oben). Es erstellt einen neuen QThread und einen neuen ChildrenWatcher, der dann in den neuen Thread verschoben wird. Dann verbinde ich drei Signale, die meinen Manager über das Schicksal seiner Kindprozesse informieren (exited / signalled / gott-weiß-was-passiert). Dann beginnt der Hauptspaß.

Ich verbinde QThread :: started () mit der Methode ChildrenWatcher :: watch (), so dass es gestartet wird, sobald der Thread fertig ist. Da der Beobachter im neuen Thread lebt, wird hier die Methode watch () ausgeführt (die Verbindung in der Warteschlange wird verwendet, um den Slot aufzurufen).

Dann verbinde ich das ChildProcesses :: stopped () Signal mit Qt :: DirectConnection zum ChildrenWatcher :: stop () Slot, weil ich es asynchron tun muss. Dies ist erforderlich, damit mein Thread beendet wird, wenn der ChildProcesses-Manager nicht mehr benötigt wird. Die stop () -Methode sieht folgendermaßen aus:

 void ChildrenWatcher::stop() { mutex.lock(); stopped = true; mutex.unlock(); } 

Und dann ChildrenWatcher :: watch ():

 void ChildrenWatcher::watch() { while (!isStopped()) { // Blocking waitpid() call here. // Maybe emit one of the three informational signals here too. } // Self-destruct now! deleteLater(); } 

Oh, und die isStopped () Methode ist nur eine bequeme Möglichkeit, einen Mutex in der while () Bedingung zu verwenden:

 bool ChildrenWatcher::isStopped() { bool stopped; mutex.lock(); stopped = this->stopped; mutex.unlock(); return stopped; } 

Was hier passiert, ist, dass ich das Stopp-Flag setze, wenn ich fertig bin, und das nächste Mal, wenn isStopped () aufgerufen wird, gibt es false zurück und der Thread endet.

Was passiert also, wenn die Watch () – Schleife endet? Es ruft deleteLater () auf, so dass das Objekt sich selbst zerstört, sobald die Kontrolle an die Thread-Ereignisschleife zurückgegeben wird, die direkt nach dem Aufruf von deleteLater () erfolgt (wenn watch () zurückkehrt). Wenn Sie zurück zu ChildProcesses :: start () gehen, können Sie sehen, dass eine Verbindung vom “destroyed” -Signal des Watcher zum “quit” -Slot des Threads besteht. Dies bedeutet, dass der Thread automatisch beendet wird, wenn der Beobachter fertig ist. Und wenn es fertig ist, zerstört es sich auch selbst, weil sein eigenes finished () Signal mit seinem deleteLater () Slot verbunden ist.

Das ist ungefähr die gleiche Idee wie Maya, aber weil ich das Selbstzerstörungs-Idiom verwende, muss ich nicht von der Reihenfolge abhängig sein, in der die Slots aufgerufen werden. Es ist immer zuerst selbstzerstörerisch, haltet den Thread später auf, dann zerstört es sich auch selbst. Ich könnte ein fertiges () Signal im Worker definieren und es dann mit seinem eigenen deleteLater () verbinden, aber das würde nur eine Verbindung mehr bedeuten. Da ich kein fertiges () Signal für irgendeinen anderen Zweck brauche, habe ich einfach deleteLater () vom Arbeiter selbst aufgerufen.

Maya erwähnt auch, dass Sie keine neuen QObjects im Konstruktor des Arbeiters zuweisen sollten, weil sie nicht in dem Thread leben, in den Sie den Worker verschieben. Ich würde es trotzdem tun, weil OOP so funktioniert. Stellen Sie nur sicher, dass alle diese QObjects Kinder des Worker sind (dh, verwenden Sie den QObject (QObject *) – Konstruktor) – moveToThread () verschiebt alle untergeordneten Elemente mit dem Objekt, das verschoben wird. Wenn Sie wirklich QObjects benötigen, die keine untergeordneten Elemente Ihres Objekts sind, überschreiben Sie moveToThread () in Ihrem Worker, sodass auch alle erforderlichen Elemente verschoben werden.

Um nicht von @ sergey-tachenovs exzellenter Antwort abzulenken, aber in Qt5 können Sie aufhören, SIGNAL und SLOT zu verwenden, Ihren Code zu vereinfachen und den Vorteil der Kompilierzeitprüfung zu haben:

 void ChildProcesses::start() { QThread *childrenWatcherThread = new QThread(); ChildrenWatcher *childrenWatcher = new ChildrenWatcher(); childrenWatcher->moveToThread(childrenWatcherThread); // These three signals carry the "outcome" of the worker job. connect(childrenWatcher, ChildrenWatcher::exited, ChildProcesses::onChildExited); connect(childrenWatcher, ChildrenWatcher::signalled, ChildProcesses::onChildSignalled); connect(childrenWatcher, ChildrenWatcher::stateChanged, ChildProcesses::onChildStateChanged); // Make the watcher watch when the thread starts: connect(childrenWatcherThread, QThread::started, childrenWatcher, ChildrenWatcher::watch); // Make the watcher set its 'stop' flag when we're done. // This is performed while the watch() method is still running, // so we need to execute it concurrently from this thread, // hence the Qt::DirectConnection. The stop() method is thread-safe // (uses a mutex to set the flag). connect(this, ChildProcesses::stopped, childrenWatcher, ChildrenWatcher::stop, Qt::DirectConnection); // Make the thread quit when the watcher self-destructs: connect(childrenWatcher, ChildrenWatcher::destroyed, childrenWatcherThread, QThread::quit); // Make the thread self-destruct when it finishes, // or rather, make the main thread delete it: connect(childrenWatcherThread, QThread::finished, childrenWatcherThread, QThread::deleteLater); childrenWatcherThread->start(); } 

Unterklassifizieren der Qthread-class wird weiterhin den Code in dem Ursprungsthread ausführen. Ich wollte einen UDP-Listener in einer Anwendung ausführen, die bereits den GUI-Thread (den Hauptthread) verwendet. Während mein UDP-Listener einwandfrei funktionierte, wurde meine GUI eingefroren, da sie von den untergeordneten Qthread-Event-Handlern blockiert wurde. Ich denke, was g19fanatic gepostet hat, ist korrekt, aber Sie werden auch den Arbeitsthread benötigen, um das Objekt erfolgreich in den neuen Thread zu migrieren. Ich habe diesen Beitrag gefunden, der detailliert die Do’s und Dont’s des Threading in QT beschreibt.

Muss gelesen werden, bevor Sie sich entscheiden, QThread abzuleiten!