什么是正确的方式来实现一个QThread …(例如请…)

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

以下是取自4.7 Qthread文档…

要创build自己的线程,创build子类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(); } 

所以在我创build的每一个线程中,我已经完成了这个工作,对于大多数事情来说它工作得很好(我没有在我的任何对象中实现moveToThread(this),而且效果很好)。

上周我遇到了一个麻烦(设法通过在我创build我的对象的地方工作来解决这个问题),并发现了以下博客post 。 这里基本上说,子类化QThread真的不是正确的方法来做到这一点(和文档是不正确的)。

这是来自Qt开发人员,所以乍一看我感兴趣,经过进一步思考,同意他的观点。 遵循面向对象的原则,你真的只想要inheritance一个类,以进一步增强这个类…不只是直接使用类的方法…这就是为什么你实例…

比方说,我想移动一个自定义的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线程的一个适当的例子。

关于唯一可以考虑添加的事情是进一步声明QObject与单个线程有亲和力。 这通常是创buildQObject的线程。 所以如果你在应用程序的主线程中创build一个QObject ,并想在另一个线程中使用它,则需要使用moveToThread()来更改关联。

这样可以节省不得不在QThread创build子类并在run()方法中创build你的对象,从而保持你的东西很好的封装。

那个博客文章确实包含了一个例子的链接。 这很短,但它显示了基本的想法。 创build你的QObject ,连接你的信号,创build你的QThread ,将你的QObjects移动到QThread并启动线程。 信号/插槽机制将确保线程边界正确和安全地交叉。

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

我知道Qt还有一些其他的线程工具,除了可能值得熟悉的线程之外,我还没有这样做:)

下面是如何正确使用QThread的一个例子 ,但它有一些问题,这反映在评论。 特别是,由于时隙的执行顺序没有严格规定,因此会导致各种问题。 2013年8月6日发表的评论给出了一个很好的想法如何处理这个问题。 我在程序中使用了类似的东西,下面是一些示例代码来澄清。

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

 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类是一个subprocesspipe理器,它使用spawn()调用启动新的subprocess,保存当前正在运行的进程的列表等等。 但是,它需要跟踪子状态,这意味着在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()方法(见上)。 它创build一个新的QThread和一个新的ChildrenWatcher,然后移动到新的线程。 然后,我连接了三个信号,告诉我的经理有关其subprocess的命运(退出/发信号/知道发生了什么事)。 然后开始主要乐趣。

我将QThread :: started()连接到ChildrenWatcher :: watch()方法,以便在线程就绪后立即启动它。 由于观察者位于新线程中,所以watch()方法被执行(队列连接用于调用插槽)。

然后我使用Qt :: DirectConnection将ChildProcesses :: stopped()信号连接到ChildrenWatcher :: stop()插槽,因为我需要asynchronous执行它。 这是需要的,所以当不再需要ChildProcessespipe理器时,线程停止。 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(),你可以看到有一个从watcher的destroy()信号到线程的quit()槽的连接。 这意味着当观察者完成时线程自动完成。 当它完成时,它也自毁,因为它自己的finished()信号连接到它的deleteLater()插槽。

这和Maya发布的几乎是一样的想法,但是因为我使用了自毁式成语,所以我不需要依赖插槽的调用顺序。 它总是先自毁,后来停线,然后自毁。 我可以在worker中定义一个finished()信号,然后将它连接到它自己的deleteLater(),但那只会意味着更多的连接。 由于我不需要完成()信号用于任何其他目的,所以我select从worker自己调用deleteLater()。

Maya还提到,你不应该在worker的构造函数中分配新的QObject,因为它们不会在你移动worker的线程中。 我会说,无论如何,因为这是OOP的工作方式。 只要确保所有这些QObject是工作人员的孩子(即使用QObject(QObject *)构造函数)。moveToThread()将移动所有的孩子和被移动的对象。 如果你真的需要有不是你的对象的子对象的QObject,那么在你的worker中覆盖moveToThread(),这样它就可以移动所有必要的东西。

不要贬低@ 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(); } 

子类化qthread类仍然会运行原始线程中的代码。 我想在已经使用GUI线程(主线程)的应用程序中运行udp侦听器,而当我的udp侦听器完美工作时,我的GUI被冻结,因为它被qcread事件处理程序所阻塞。 我想什么g19fanatic张贴是正确的,但你也将需要工作线程成功地将对象迁移到新的线程。 我发现这篇文章详细描述了QT中线程的Do和Do's。

在决定子类QThread之前,必须先阅读!