it-swarm.com.de

Was ist der richtige Weg, um einen QThread zu implementieren ... (Beispiel bitte ...)

In der Qt-Dokumentation für QThread heißt es, eine Klasse aus QThread zu erstellen und die Ausführungsmethode zu implementieren.

Das Folgende ist der 4.7 Qthread-Dokumentation entnommen ...

Um Ihre eigenen Threads zu erstellen, unterordnen Sie QThread und implementieren Sie run () erneut. Beispielsweise:

 class MyThread : public QThread
 {
 public:
     void run();
 };

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

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

Ich habe letzte Woche einen Haken bekommen (ich habe es geschafft, indem ich herumgearbeitet habe, wo ich meine Objekte erstellt habe) und fand das folgenden Blog-Beitrag . Hier wird im Grunde gesagt, dass die Unterklassifizierung von QThread nicht die richtige Vorgehensweise ist (und dass die Dokumentation falsch ist).

Dies kommt von einem Qt-Entwickler, daher war ich auf den ersten Blick interessiert und stimme ihm bei weiteren Überlegungen zu. Nach den Grundsätzen von OO) wollen Sie wirklich nur eine Klasse unterordnen, um diese Klasse weiter zu verbessern ... nicht nur um die Klassenmethoden direkt zu verwenden ... deshalb instanziieren Sie ...

Nehmen wir an, ich wollte eine benutzerdefinierte QObject-Klasse in einen Thread verschieben. Was wäre die richtige Vorgehensweise? In diesem Blog-Beitrag "sagt" er, dass er irgendwo ein Beispiel hat ... aber wenn jemand es mir weiter erklären könnte, wäre es sehr dankbar!

Update:

Da diese Frage so viel Beachtung findet, finden Sie hier ein Copy & Paste der 4.8-Dokumentation mit der "richtigen" Art, einen QThread zu implementieren.

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 Member, das unnötig ist und in ihrem Beispiel niemals verwendet wird. Entfernen Sie das Teil und es ist ein gutes Beispiel für das Einfädeln von Qt.

61
g19fanatic

Das einzige, was ich hinzufügen kann, ist die Feststellung, dass QObjects eine Affinität zu einem einzelnen Thread haben. Dies ist normalerweise der Thread, der das QObject erstellt. Wenn Sie also im Haupt-Thread der App ein QObject erstellen und es in einem anderen Thread verwenden möchten, müssen Sie moveToThread() verwenden, um die Affinität zu ändern.

Dies erspart die Unterklasse von QThread und das Erstellen Ihrer Objekte in der run() -Methode, wodurch Ihre Inhalte schön gekapselt bleiben.

Dieser Blog-Beitrag enthält einen Link zu einem Beispiel . Es ist ziemlich kurz, aber es zeigt die Grundidee. Erstelle dein QObjects, verbinde deine Signale, erstelle dein QThread, verschiebe dein QObjects auf das QThread und starte den Thread. Die Signal-/Schlitzmechanismen stellen sicher, dass die Fadengrenzen richtig und sicher überschritten 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ß, Qt hat einige andere Nice Threading-Möglichkeiten Jenseits von Threads, die es wahrscheinlich wert sind, sich mit ihnen vertraut zu machen, aber ich habe es noch nicht getan :)

31
Arnold Spence

Hier ist ein Beispiel für die korrekte Verwendung von QThread , aber es gibt einige Probleme, die sich in den Kommentaren widerspiegeln. Insbesondere kann es zu verschiedenen Problemen kommen, da die Reihenfolge, in der die Slots ausgeführt werden, nicht genau definiert ist. Der Kommentar vom 6. August 2013 gibt eine gute Vorstellung davon, wie man mit diesem Problem umgeht. Ich verwende so etwas in meinem Programm, und hier ist ein Beispielcode zur Verdeutlichung.

Die Grundidee ist dieselbe: Ich erstelle eine QThread-Instanz, die in meinem Hauptthread lebt, eine Worker-Klasseninstanz, die in dem neu erstellten Thread lebt, 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();
}

Einige Hintergrundinformationen:

Die ChildProcesses-Klasse ist ein untergeordneter Prozessmanager, der mit spawn () -Aufrufen neue untergeordnete Prozesse startet, die Liste der derzeit ausgeführten Prozesse aufzeichnet usw. Es muss jedoch die untergeordneten Zustände im Auge behalten, was bedeutet, dass der Aufruf waitpid () unter Linux oder WaitForMultipleObjects unter Windows verwendet wird. Früher habe ich diese mit einem Timer im nicht blockierenden Modus aufgerufen, jetzt möchte ich eine schnellere Reaktion, dh Blockierungsmodus. Hier kommt der Faden ins Spiel.

Die ChildrenWatcher-Klasse 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 untergeordneten Prozesse informieren (verlassen/signalisiert/Gott-weiß-was-passiert ist). Dann fängt der Hauptspaß an.

Ich verbinde QThread :: started () mit der ChildrenWatcher :: watch () -Methode, damit es gestartet wird, sobald der Thread fertig ist. Da der Watcher im neuen Thread lebt, wird dort die watch () -Methode ausgeführt (die Verbindung in der Warteschlange wird zum Aufrufen des Slots verwendet).

Dann verbinde ich das ChildProcesses :: stops () Signal mit Qt :: DirectConnection mit dem ChildrenWatcher :: stop () Slot, weil ich es asynchron machen muss. Dies ist erforderlich, damit mein Thread angehalten 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 () -Zustand 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 sein muss, und dann, wenn das nächste Mal isStopped () aufgerufen wird, false zurückgibt und der Thread endet.

Was passiert also, wenn die watch () -Schleife endet? Es ruft deleteLater () auf, sodass sich das Objekt selbst zerstört, sobald die Steuerung an die Thread-Ereignisschleife zurückgegeben wird, die unmittelbar nach dem Aufruf von deleteLater () erfolgt (wenn watch () zurückkehrt). Wenn Sie zu ChildProcesses :: start () zurückkehren, können Sie feststellen, dass eine Verbindung zwischen dem Signal destroyed () des Beobachters und dem Slot quit () 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 selbst, weil sein eigenes finish () -Signal mit seinem deleteLater () -Slot verbunden ist.

Dies ist so ziemlich die gleiche Idee wie bei Maya, aber da ich die Selbstzerstörungssprache verwende, muss ich nicht auf die Reihenfolge angewiesen sein, in der die Slots aufgerufen werden. Es zerstört sich immer zuerst selbst, stoppt den Thread später und zerstört sich dann auch selbst. Ich könnte ein finished () - Signal im Worker definieren und es dann mit seinem eigenen deleteLater () verbinden, aber das würde nur eine Verbindung mehr bedeuten. Da ich für keinen anderen Zweck ein finish () -Signal benötige, habe ich mich dafür entschieden, deleteLater () nur vom Worker selbst aufzurufen.

Maya erwähnt auch, dass Sie keine neuen QObjects im Konstruktor des Workers zuweisen sollten, da diese nicht in dem Thread leben, in den Sie den Worker verschieben. Ich würde sagen, mach es trotzdem, weil das so ist OOP funktioniert. Stelle nur sicher, dass alle diese QObjects Kinder des Workers sind (benutze also den QObject (QObject *) -Konstruktor) - moveToThread ( ) verschiebt alle untergeordneten Objekte zusammen mit dem zu verschiebenden Objekt. Wenn Sie wirklich QObjects benötigen, die keine untergeordneten Objekte Ihres Objekts sind, überschreiben Sie moveToThread () in Ihrem Worker, damit auch alle erforderlichen Elemente verschoben werden.

9
Sergei Tachenov

Um die hervorragende Antwort von @ sergey-tachenov nicht zu beeinträchtigen, können Sie in Qt5 die Verwendung von SIGNAL und SLOT einstellen, Ihren Code vereinfachen und den Vorteil der Überprüfung der Kompilierungszeit nutzen:

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();
}
3
parsley72

wenn Sie eine Unterklasse für die Klasse qthread erstellen, wird der Code weiterhin im ursprünglichen Thread ausgeführt. Ich wollte einen udp-Listener in einer Anwendung ausführen, die bereits den GUI-Thread (den Hauptthread) verwendet, und während mein udp-Listener einwandfrei funktionierte, war meine GUI eingefroren, da sie von den untergeordneten qthread-Ereignishandlern blockiert wurde. Ich denke, was g19fanatic gepostet hat, ist korrekt, aber Sie benötigen auch den Worker-Thread, um das Objekt erfolgreich in den neuen Thread zu migrieren. Ich habe this post gefunden, der ausführlich die Do's und Dont's des Threadings in QT beschreibt.

Muss gelesen werden, bevor Sie sich für die Unterklasse von QThread entscheiden!

2
Zaid