【问题标题】:Creating QNetworkAccessManager in another thread在另一个线程中创建 QNetworkAccessManager
【发布时间】:2018-05-31 21:08:28
【问题描述】:

我在另一个线程中创建了一个 QNetworkAccessManager。 该网络只能在 MyMegaThread 中使用。
QNetworkAccessManager 是从线程的run 方法创建的:

mp_manager.reset(new QNetworkAccessManager{this});

在创建时,我在控制台中收到这样的消息:

QObject: Cannot create children for a parent that is in a different thread.
(Parent is MyMegaThread(0x237eabd0ee0), parent's thread is QThread(0x237e70742a0), current thread is MyMegaThread(0x237eabd0ee0)

此消息完全无害,但我想知道经理应该有哪个父母。
我怀疑这是因为 MyMegaThread 实例是在主线程中创建的,但我需要在 MyMegaThread 中创建父级。

这样做的惯用方式是什么?

【问题讨论】:

标签: c++ multithreading qt networking


【解决方案1】:

父线程是MyMegaThread(0x237eabd0ee0),父线程是 QThread(0x237e70742a0),当前线程是MyMegaThread(0x237eabd0ee0)

该问题与QNetworkAccessManager 无关。

这是重现警告的演示。

#include <QDebug>
#include <QThread>

class MyMegaThread : public QThread
{
    Q_OBJECT
public:
    using QThread::QThread;

protected:
    void run() override {
        qDebug()<<QThread::currentThread()<<this->thread();
        new QObject(this);
    }
};

// main
MyMegaThread m;
m.start();

输出:

MyMegaThread(0x60fe18) QThread(0x16a7c48)

这是QObject的规则:

所有 QObject 必须与其父对象存在于同一线程中。 因此:

如果涉及的两个 QObjects 生活在不同的地方,setParent() 将失败 线程。当一个 QObject 被移动到另一个线程时,它的所有子 也会自动移动。 moveToThread() 将失败,如果 QObject 有一个父对象。如果 QObjects 是在 QThread::run() 中创建的, 它们不能成为 QThread 对象的子对象,因为 QThread 不在调用 QThread::run() 的线程中。

http://doc.qt.io/qt-5/qobject.html#thread-affinity

必须确保运行QThread 的代码new QObject 与给定的父QObject 线程相同。

mp_manager.reset(new QNetworkAccessManager{this});

【讨论】:

    【解决方案2】:

    不,该消息根本不是无害的。您创建的对象有一个空父对象,并且没有对线程关联的引用,因此它的thread() 方法可能随时返回一个悬空指针。它不能安全地使用定时器,也不能接收跨线程调用。它基本上是无用的对象,您要求执行未定义的行为。这不应该是一个警告,而是一个失败。 Qt 允许您继续,这对您造成了伤害。

    这样做的惯用方式是首先不要派生自QThreadQThread 是一个线程句柄。它包装了系统资源。将您的所有功能放入常规的QObject 中,然后移至QThread 中。在任何线程(包括主线程)上无休止地“做事”的惯用方法是使用零持续时间计时器。请注意,零持续时间计时器与计时完全无关。它们本质上是事件循环句柄,称它们为计时器是用词不当。

    即:

    // https://github.com/KubaO/stackoverflown/tree/master/questions/thread-simple-50632807
    #include <QtNetwork>
    
    class Thread final : public QThread {
       Q_OBJECT
    public:
       void takeObject(QObject *obj) {
          obj->moveToThread(this);
       }
       ~Thread() override {
          requestInterruption();
          quit();
          wait();
       }
    };
    
    class Class : public QObject {
       Q_OBJECT
       QBasicTimer m_workTimer;
       QNetworkAccessManager m_manager{this};
       void doWorkChunk() {
          qDebug() << "tick...";
          QThread::sleep(1); // emulate a blocking operation
       }
    protected:
       void timerEvent(QTimerEvent *ev) override {
          if (ev->timerId() != m_workTimer.timerId())
             return;
          doWorkChunk();
       }
    public:
       explicit Class(QObject *parent = {}) : QObject(parent) {
          m_workTimer.start(0, this);
       }
    };
    
    int main(int argc, char *argv[]) {
       QCoreApplication app(argc, argv);
       Class object;
       Thread workThread;
       workThread.start();
       workThread.takeObject(&object);
       QTimer::singleShot(3000, &QCoreApplication::quit);
       return app.exec();
    }
    #include "main.moc"
    

    QBasicTimer::stop: Failed. Possibly trying to stop from a different thread 警告相对温和,表示内部计时器句柄泄漏。有关解决方法,请参阅this answer

    【讨论】:

    • 您的代码看起来不像派生的 QThread 那样健壮,但我明白了您的想法。会尝试一些重构。
    猜你喜欢
    • 1970-01-01
    • 2023-04-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多