实现QThread的正确方法是什么......(请举例......)

时间:2010-11-04 01:31:53

标签: multithreading qt qt4 qthread

QThread的Qt文档说要从QThread创建一个类,并实现run方法。

以下内容摘自4.7 Qthread文档......

  

要创建自己的线程,请继承QThread并重新实现run()。例如:

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

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

所以在我创建的每一个线程中,我都做到了这一点,并且对于大多数事情来说它工作得很好(我没有在我的任何对象中实现moveToThread(this)并且效果很好)。

我上周遇到了麻烦(设法通过解决我创建对象的地方来解决问题)并找到了following blog post。这里基本上说,子类化QThread确实不是正确的方法(并且文档不正确)。

这是来自Qt的开发人员,所以乍一看我很感兴趣,经过进一步的反思,同意他。遵循面向对象原则,你真的只想继承一个类来进一步增强该类......不要直接使用类方法......这就是你实例化的原因......

让我们说我想将一个自定义QObject类移动到一个线程......这样做的“正确”方法是什么?在那篇博客文章中,他说'他在某个地方有一个例子......但如果有人能够向我进一步解释它,我将非常感激!

更新

由于这个问题引起了如此多的关注,这里是4.8文档的复制和粘贴,以及“正确”的方式来实现QThread。

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 &);
 };

我仍然认为值得指出的是,它们包含一个额外的Worker::workerThread成员,这是不必要的,并且从未在他们的示例中使用过。删除该部分,这是如何在Qt中进行线程的正确示例。

5 个答案:

答案 0 :(得分:31)

关于我唯一想要添加的内容是进一步说明QObject与单个线程有亲缘关系。这通常是创建QObject的线程。因此,如果您在应用程序的主线程中创建QObject并希望在另一个线程中使用它,则需要使用moveToThread()来更改关联。

这节省了必须子类QThread并在run()方法中创建对象,从而保持您的东西很好地封装。

该博客帖子确实包含指向example的链接。它很短,但它显示了基本的想法。创建QObject,连接信号,创建QThread,将QObjects移至QThread并启动主题。信号/插槽机制将确保正确且安全地交叉线程边界。

如果必须在该机制之外的对象上调用方法,则可能必须引入同步。

我知道Qt还有一些其他很好的threading facilities线程可能值得熟悉,但我还没有这样做:)

答案 1 :(得分:9)

这是one example of how to use QThread correctly,但它有一些问题,这些问题会反映在评论中。特别是,由于没有严格定义执行槽的顺序,因此可能导致各种问题。 2013年8月6日发布的评论很好地解决了如何处理这个问题。我在我的程序中使用了类似的东西,这里有一些示例代码来澄清。

基本思想是一样的:我创建一个QThread实例,它存在于我的主线程中,一个工作者类实例,它存在于我创建的新线程中,然后我连接所有信号。

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

一些背景知识:

ChildProcesses类是一个子进程管理器,它使用spawn()调用启动新的子进程,保留当前正在运行的进程列表等等。但是,它需要跟踪子状态,这意味着在Linux上使用waitpid()调用或在Windows上使用WaitForMultipleObjects。我曾经使用计时器在非阻塞模式下调用它们,但现在我想要更快速的反应,这意味着阻塞模式。这就是线程进入的地方。

ChildrenWatcher类定义如下:

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);
};

这是怎么回事。当所有这些东西都启动时,调用ChildProcess :: start()方法(见上文)。它创建了一个新的QThread和一个新的ChildrenWatcher,然后将其移动到新的线程。然后我连接三个信号,通知我的经理关于其子进程的命运(退出/发信号/上帝知道发生了什么)。然后开始主要的乐趣。

我将QThread :: started()连接到ChildrenWatcher :: watch()方法,以便在线程准备好后立即启动它。由于观察者生活在新线程中,因此执行watch()方法(排队连接用于调用插槽)。

然后我使用Qt :: DirectConnection将ChildProcesses :: stopped()信号连接到ChildrenWatcher :: stop()槽,因为我需要异步执行它。这是必需的,所以我的线程在不再需要ChildProcesses管理器时停止。 stop()方法如下所示:

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

然后是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();
}

哦,isStopped()方法只是在while()条件下使用互斥的一种方便的方法:

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

所以这里发生的是我在需要完成时设置了停止标志,然后下次调用isStopped(),它返回false并且线程结束。

那么watch()循环结束时会发生什么?它调用deleteLater(),因此只要控件返回到线程事件循环,对象就会自行破坏,这发生在deleteLater()调用之后(当watch()返回时)。回到ChildProcesses :: start(),您可以看到从观察者的destroyed()信号到线程的quit()槽的连接。这意味着当观察者完成时线程自动完成。当它完成时,它也会自毁,因为它自己的finished()信号连接到它的deleteLater()插槽。

这与Maya发布的几乎相同,但由于我使用自毁语法,我不需要依赖于调用插槽的顺序。它总是先自我毁灭,稍后停止线程,然后它自我毁灭。我可以在worker中定义一个finished()信号,然后将它连接到自己的deleteLater(),但这只会意味着一个连接更多。由于我不需要为任何其他目的使用finished()信号,我选择只从工作者本身调用deleteLater()。

Maya还提到你不应该在worker的构造函数中分配新的QObject,因为它们不会存在于你将worker移动到的线程中。无论如何我都会这么做,因为这就是OOP的工作方式。只要确保所有这些QObject都是worker的子节点(即使用QObject(QObject *)构造函数) - moveToThread()将所有子节点与正在移动的对象一起移动。如果你真的需要QObject不是你对象的子对象,那么在你的worker中覆盖moveToThread(),这样它就会移动所有必要的东西。

答案 2 :(得分:3)

不要贬低@ sergey-tachenov的优秀答案,但在Qt5中,您可以停止使用SIGNAL和SLOT,简化代码并具有编译时检查的优势:

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 :(得分:2)

继承qthread类仍然会在原始线程中运行代码。我想在已经使用GUI线程(主线程)的应用程序中运行一个udp监听器,而我的udp监听器工作正常,我的GUI被冻结,因为它被子类化的qthread事件处理程序阻止。我认为g19fanatic发布的内容是正确的,但您还需要工作线程才能成功地将对象迁移到新线程。我找到了this帖子,详细描述了QT中线程的Do和Dont。

在您决定继承QThread之前必须阅读!

答案 4 :(得分:0)

我在Qt5中的最佳实践线程模型版本很简单: worker.h

/*
* This is a simple, safe and best-practice way to demonstrate how to use threads in Qt5.
* Copyright (C) 2019 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#ifndef _WORKER_H
#define _WORKER_H

#include <QtCore/qobject.h>
#include <QtCore/qbytearray.h>
#include <QtCore/qthread.h>
#include <QtGui/qevent.h>

namespace concurrent {

    class EventPrivate;
    class Event : public QEvent {
    public:
        enum {
            EventType1 = User + 1
        };

        explicit Event(QEvent::Type);
        Event(QEvent::Type, const QByteArray&);

        void setData(const QByteArray&);
        QByteArray data() const;

    protected:
        EventPrivate* d;
    };

    class WorkerPrivate;
    /* A worker class to manage one-call and permanent tasks using QThread object */
    class Worker : public QObject {
        Q_OBJECT

    public:
        Worker(QThread*);
        ~Worker();

    protected slots:
        virtual void init();

    protected:
        bool event(QEvent*) override;

    protected:
        WorkerPrivate* d;

    signals:
        /* this signals is used for one call type worker */
        void finished(bool success);
    };

} // namespace concurrent

#endif // !_WORKER_H

worker.cpp

/*
* This is a simple, safe and best-practice way to demonstrate how to use threads in Qt5.
* Copyright (C) 2019 Iman Ahmadvand
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

#include "worker.h"

using namespace concurrent;

class concurrent::EventPrivate {
public:
    QByteArray data;
};

Event::Event(QEvent::Type t):QEvent(t), d(new EventPrivate) {
    setAccepted(false);
}

Event::Event(QEvent::Type t, const QByteArray& __data) : Event(t) {
    setData(__data);
}

void Event::setData(const QByteArray& __data) {
    d->data = __data;
}

QByteArray Event::data() const {
    return d->data;
}



class concurrent::WorkerPrivate {
public:
    WorkerPrivate() {

    }
};

Worker::Worker(QThread* __thread) :QObject(nullptr), d(new WorkerPrivate) {
    moveToThread(__thread);

    QObject::connect(__thread, &QThread::started, this, &Worker::init);
    QObject::connect(this, &Worker::finished, __thread, &QThread::quit);
    QObject::connect(__thread, &QThread::finished, __thread, &QThread::deleteLater);
    QObject::connect(__thread, &QThread::finished, this, &Worker::deleteLater);
}

Worker::~Worker() {
    /* do clean up if needed */
}

void Worker::init() {
    /* this will called once for construction and initializing purpose */
}

bool Worker::event(QEvent* e) {
    /* event handler */
    if (e->type() == Event::EventType1) {
        /* do some work with event's data and emit signals if needed */
        auto ev = static_cast<Event*>(e);
        ev->accept();
    }
    return QObject::event(e);
}

usage.cpp

#include <QtCore/qcoreapplication.h>
#include "worker.h"

using namespace concurrent;

Worker* create(bool start) {
    auto worker = new Worker(new QThread);
    if (start)
        worker->thread()->start();

    return worker;
}

int main(int argc, char *argv[]) {
    QCoreApplication app(argc, argv);
    auto worker = create(true);
    if (worker->thread()->isRunning()) {
        auto ev = new Event(static_cast<QEvent::Type>(Event::EventType1));
        qApp->postEvent(worker, ev, Qt::HighEventPriority);
    }
    return app.exec();
}