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

Posted

技术标签:

【中文标题】实现 QThread 的正确方法是啥...(请举例...)【英文标题】:what is the correct way to implement a QThread... (example please...)实现 QThread 的正确方法是什么...(请举例...) 【发布时间】:2011-05-04 20:05:19 【问题描述】:

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 开发人员,所以乍一看我很感兴趣,经过进一步思考,同意他的观点。遵循 OO 原则,您真的只想对一个类进行子类化以进一步增强该类...而不是直接使用类方法...这就是您实例化的原因...

假设我想将一个自定义 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 中执行线程的一个正确示例。

【问题讨论】:

文档只说不鼓励在 QThread 子类中引入新插槽。没有提到从 QThread 类派生。从 QThread 派生遵循与 Delphi/C++ Builder 的 TThread 相同的范例。 woboq.com/blog/qthread-you-were-not-doing-so-wrong.html 他们的示例代码将无法编译,除非您将第一行 'connect' 行修改为 workerThread 的地址,如下所示:connect(&workerThread, SIGNAL(finished()) , 工人, SLOT(deleteLater())); 我认为你应该删除“QThread workerThread;”在 WorkerObject 中。误会了 【参考方案1】:

我能想到的唯一要补充的就是进一步说明QObjects 与单个线程有关联。这通常是创建QObject 的线程。所以如果你在应用的主线程中创建了QObject,并且想在另一个线程中使用它,你需要使用moveToThread()来改变亲和力。

这省去了子类化 QThread 并在 run() 方法中创建对象,从而使您的东西得到很好的封装。

该博文确实包含指向example 的链接。它很短,但它显示了基本思想。创建您的QObjects,连接您的信号,创建您的QThread,将您的QObjects 移动到QThread 并启动线程。信号/槽机制将确保正确且安全地跨越线程边界。

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

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

【讨论】:

链接的例子还说他们做 QThread 的子类并实现 run() 来做一个 exec()。这将基本上启动事件循环并使连接能够做他们的事情......根据我的收集,你不应该这样做(从我列出的原始帖子)或者我误解了你仍然需要做这个? 你理解正确。从 Qt 4.4 开始,run() 的默认实现会为您执行此操作。【参考方案2】:

这里是one example of how to use QThread correctly,但它有一些问题,这些问题反映在 cmets 中。特别是,由于插槽的执行顺序没有严格定义,因此可能会导致各种问题。 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() 方法,以便在线程准备好后立即启动它。由于 watcher 位于新线程中,因此 watch() 方法会在该线程中执行(队列连接用于调用 slot)。

然后我使用 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(),可以看到观察者的destroy() 信号与线程的quit() 槽之间存在连接。这意味着当观察者完成时线程会自动结束。当它完成时,它也会自毁,因为它自己的finished() 信号连接到它的deleteLater() 槽。

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

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

【讨论】:

虽然我感谢您展示您的基于事件的管理器的实现,但这与这个问题无关。问题在于 Qt 用于推荐线程的实现方式与“正确”的实现方式(现在在当前文档中更好)之间的文档差异... @g19,我在搜索使用 QThread 的正确方法时发现了您的问题(以及其他几个页面)。在那之后我才实现了这个,然后我意识到这正是我在谷歌上搜索的目标。所以我发布了它,希望其他正在谷歌搜索“正确使用 QThread 的方法”的人会发现这很有用。 @g19,哦,我正在使用 Qt 4.6 或其他东西,所以我不知道他们更改了文档。但是文档仍然非常有限,并且没有解释如何做我需要做的事情(以及许多其他人需要做的事情),所以我想这个问题仍然有效。 现在他们在 SO 上为此类内容推广“问一个常见问题”和“自己回答”,因此您可以将其转换为一个单独的问题(列出所有您详细发现的陷阱),并获得一些不错的支持:) 我发现您的回答非常有用,但如果您将完整的代码发布在某处(例如 Gist)以获取所有详细信息,那就太好了。【参考方案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();

【讨论】:

【参考方案4】:

子类化 qthread 类仍将在原始线程中运行代码。我想在已经使用 GUI 线程(主线程)的应用程序中运行 udp 侦听器,而当我的 udp 侦听器工作正常时,我的 GUI 被冻结,因为它被子类 qthread 事件处理程序阻塞。我认为 g19fanatic 发布的内容是正确的,但您还需要工作线程成功地将对象迁移到新线程。我发现this 帖子详细描述了 QT 中线程的注意事项。

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

【讨论】:

不正确。在重写函数 run() 中运行的代码将在新线程上运行。 来自 Qt 文档:重要的是要记住 QThread 实例存在于实例化它的旧线程中,而不是在调用 run() 的新线程中。这意味着 QThread 的所有排队槽都将在旧线程中执行。【参考方案5】:

我在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();

【讨论】:

以上是关于实现 QThread 的正确方法是啥...(请举例...)的主要内容,如果未能解决你的问题,请参考以下文章

停止线程的方法是啥(当我直接从 QThread 继承时)?

重点:怎样正确的使用QThread类(注:包括推荐使用QThread线程的新方法QObject::moveToThread)

qthreadnew不释放

Qt:将事件发布到 QThread 的正确方法?

这是使用 QThread 的正确方法吗?

如何使用 matplotlib 和 pyplot 正确实现 QThread