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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4093159/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-10 01:11:39  来源:igfitidea点击:

what is the correct way to implement a QThread... (example please...)

multithreadingqtqt4qthread

提问by g19fanatic

The Qt documentation for QThread says to create a class from QThread, and to implement the run method.

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

Below is taken from the 4.7 Qthread documentation...

以下摘自 4.7 Qthread 文档...

To create your own threads, subclass QThread and reimplement run(). For example:

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

So in every single thread I've created, I've done just that and for most things it works just fine (I do not implement moveToThread(this) in any of my objects and it works great).

因此,在我创建的每个线程中,我都这样做了,并且对于大多数事情它都可以正常工作(我没有在我的任何对象中实现 moveToThread(this) 并且它工作得很好)。

I hit a snag last week (managed to get through it by working around where I created my objects) and found the following blog post. Here is basically says that subclassing QThread really isn't the correct way to do it (and that the documentation is incorrect).

上周我遇到了一个障碍(设法解决了我创建对象的地方)并找到了以下博客文章。这里基本上是说子类化 QThread 确实不是正确的方法(并且文档不正确)。

This is coming from a Qt developer so at first glance I was interested and upon further reflection, agree with him. Following OO principles, you really only want to subclass a class to further enhance that class... not to just use the classes methods directly... thats why you instantiate...

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

Lets say I wanted to move a custom QObject class to a thread... what would be the 'correct' way of doing it? In that blog post, he 'says' he has an example somewhere... but if someone could further explain it to me it'd be greatly appreciated!

假设我想将自定义 QObject 类移动到一个线程......什么是“正确”的做法?在那篇博文中,他“说”他在某处有一个例子……但如果有人能进一步向我解释,我将不胜感激!

Update:

更新:

Since this question gets so much attention, here is a copy and paste of the 4.8 documentation with the 'proper' way to implement a QThread.

由于这个问题得到了如此多的关注,这里是 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 &);
 };

I still believe that it is worthwhile to point out that they include an extra Worker::workerThreadmember that is unnecessary and is never used in their example. Remove that piece and it is a proper example of how to do threading in Qt.

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

采纳答案by Arnold Spence

About the only thing I can think of to add is to further state that QObjects have an affinity with a single thread. This is usually the thread that creates the QObject. So if you create a QObjectin the app's main thread and want to use it in another thread, you need to use moveToThread()to change the affinity.

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

This saves having to subclass QThreadand creating your objects in the run()method, thus keeping your stuff nicely encapsulated.

这样就不必QThreadrun()方法中创建子类和创建对象,从而使您的东西得到很好的封装。

That blog post does include a link to an example. It is pretty short but it shows the basic idea. Create your QObjects, connect your signals, create your QThread, move your QObjectsto the QThreadand start the thread. The signal/slot mechanisms will ensure that thread boundaries are crossed properly and safely.

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

You may have to introduce synchronization if you have to call methods on your object outside of that mechanism.

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

I know Qt has some other nice threading facilitiesbeyond threads that are probably worth getting familiar with but I have yet to do so :)

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

回答by Sergei Tachenov

Here's one example of how to use QThread correctly, but it has some issues with it, which are reflected in the comments. In particular, since the order in which the slots are executed isn't strictly defined, it could lead to various problems. The comment posted on August 6, 2013 gives a nice idea how to deal with this issue. I use something like that in my program, and here's some example code to clarify.

是如何正确使用 QThread一个示例,但它有一些问题,这些问题反映在评论中。特别是,由于插槽执行的顺序没有严格定义,这可能会导致各种问题。2013 年 8 月 6 日发布的评论提供了如何处理此问题的好主意。我在我的程序中使用了类似的东西,这里有一些示例代码来澄清。

The basic idea is the same: I create a QThread instance that lives in my main thread, a worker class instance that lives in the new thread I created, and then I connect all the signals.

基本思想是一样的:我创建一个位于主线程中的 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();
}

Some background:

一些背景:

The ChildProcesses class is a child process manager that starts new child processes with spawn() calls, keeps the list of the processes currently running and so on. However, it needs to keep track of the children states, which means using waitpid() call on Linux or WaitForMultipleObjects on Windows. I used to call these in non-blocking mode using a timer, but now I want more prompt reaction, which means blocking mode. That's where the thread comes in.

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

The ChildrenWatcher class is defined as follows:

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

Here how it works. When all this stuff is started, the ChildProcess::start() method is called (see above). It creates a new QThread and a new ChildrenWatcher, which is then moved to the new thread. Then I connect three signals which inform my manager about the fate of its child processes (exited/signalled/god-knows-what-happened). Then starts the main fun.

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

I connect QThread::started() to the ChildrenWatcher::watch() method so it is started as soon as the thread is ready. Since the watcher lives in the new thread, that's where the watch() method is executed (queued connection is used to call the slot).

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

Then I connect the ChildProcesses::stopped() signal to the ChildrenWatcher::stop() slot using Qt::DirectConnection because I need to do it asynchronously. This is needed so my thread stops when the ChildProcesses manager is no longer needed. The stop() method looks like this:

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

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

And then ChildrenWatcher::watch():

然后 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, and the isStopped() method is just a convenient way to use a mutex in the while() condition:

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

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

So what happens here is that I set the stopped flag when I need to finish, and then the next time isStopped() is called it returns false and the thread ends.

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

So what happens when the watch() loop ends? It calls deleteLater() so the object self-destructs as soon as control is returned to the thread event loop which happens just right after the deleteLater() call (when watch() returns). Going back to ChildProcesses::start(), you can see that there is a connection from the destroyed() signal of the watcher to the quit() slot of the thread. This means that the thread automatically finishes when the watcher is done. And when it's finished, it self-destructs too because its own finished() signal is connected to its deleteLater() slot.

那么当 watch() 循环结束时会发生什么呢?它调用 deleteLater(),因此一旦控制权返回到线程事件循环,对象就会自毁,这发生在 deleteLater() 调用之后(当 watch() 返回时)。回到ChildProcesses::start(),你可以看到从watcher的destroy()信号到线程的quit()槽有一个连接。这意味着当观察者完成时线程会自动结束。当它完成时,它也会自毁,因为它自己的 finished() 信号连接到它的 deleteLater() 槽。

This is pretty much the same idea as Maya posted, but because I use the self-destruct idiom, I don't need to depend on the sequence in which the slots are called. It's always self-destruct first, stop thread later, then it self-destructs too. I could define a finished() signal in the worker, and then connect it to its own deleteLater(), but that would only mean one connection more. Since I don't need a finished() signal for any other purpose, I chose to just call deleteLater() from the worker itself.

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

Maya also mentions that you shouldn't allocate new QObjects in the worker's constructor because they won't live in the thread you move the worker to. I'd say do it anyway because that's the way OOP works. Just make sure all those QObjects are children of the worker (that is, use the QObject(QObject*) constructor) - moveToThread() moves all the children along with the object being moved. If you really need to have QObjects that aren't children of your object, then override moveToThread() in your worker so it moves all the necessary stuff too.

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

回答by parsley72

Not to detract from @sergey-tachenov's excellent answer, but in Qt5 you can stop using SIGNAL and SLOT, simplify your code and have the advantage of compile time checking:

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

回答by Zaid

subclassing the qthread class will still run the code in the originating thread. I wanted to run a udp listener in application which is already using the GUI Thread(the main thread) and while my udp listener was working perfectly my GUI was frozen as it was blocked by the subclassed qthread event handlers. I think what g19fanatic posted is correct but you will also need the worker thread to succesfully migrate the object to the new thread. I found thispost which describes in details of the Do's and Dont's of the threading in QT.

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

Must Read before you decide to subclass QThread !

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

回答by IMAN4K

My version of best-practice thread model in Qt5is as simple as this: worker.h:

我的最佳实践线程模型版本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:

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:

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