在另一个线程中创建QObject并将其检索到当前线程=在msvc16上的调试中失败ASSERT

问题描述

我只是想在另一个线程中创建的QObject上设置父对象(对象当然已经移到了父线程之前),仅此而已!

#include <QApplication>
#include <QDebug>
#include <QThread>

class Thread : public QThread //Just a convenient Class using a lambda
{
public:
    Thread::Thread(QObject *parent = nullptr) : QThread(parent){}
    std::function<void()> todo;
protected:
    virtual void run() override{
        todo();
    }
};

int main(int argc,char *argv[])
{
    QApplication a(argc,argv);

    ///////////////////////////////////////////////
    // Change this flag to switch behavIoUrs
    bool tryToSetParentInThread = true;
    ///////////////////////////////////////////////

    QObject mainObj;
    QObject *dummy; //Just to get back obj created in thread;

    Thread thread;
    thread.todo = [&](){
        //QObject *obj = new QObject(&mainObj); //"QObject: Cannot create children for a parent that is in a different thread." ! Of course!

        // So we try this
        QObject *obj = new QObject;
        dummy = obj;

        qDebug()<<obj->thread();
        obj->movetoThread(mainObj.thread());
        qDebug()<<obj->thread(); //Check that the Thread affinity change is done

        if(tryToSetParentInThread)
            obj->setParent(&mainObj);

        QObject::connect(obj,&QObject::destroyed,[](){ //Parent mecanism is OK
            qDebug()<<"Child destroyed";
        });
    };
    thread.start();
    thread.wait();

    if(!tryToSetParentInThread)
        dummy->setParent(&mainObj);

    return 0; //No need for an event loop here
}

编辑: 也许obj->setParent(&mainObj)调用不喜欢mainObj不在调用方法..的线程中?

此示例在发行版中运行良好,但是如果您尝试使用mscv16在调试中启动此代码,则:

enter image description here

解决方法

所以我调试了qt lib,问题出在setParent()做sendEvent()而没有检查线程亲和力。这两个对象位于同一线程中,但是调用的setParent()是从另一个对象完成的。即使我的操作不是典型操作,它仍然有效。这是一个简单的错误,或者至少是一个未处理的情况。

取决于调用线程,它应该执行postevent()

enter image description here

最后,我刚刚将obj->setParent(&mainObj)替换为

QMetaObject::invokeMethod(&mainObj,[&mainObj,obj](){
 obj->setParent(&mainObj);
});

此调用自动通过队列连接完成,最后在正确的线程中执行。当然,我们必须在主线程中启动eventloop才能检索此排队的事件。

这是可以接受的解决方法

#include <QApplication>
#include <QDebug>
#include <QThread>
#include <QTimer>

class Thread : public QThread //Just a convenient Class using a lambda
{
public:
    Thread::Thread(QObject *parent = nullptr) : QThread(parent){}
    std::function<void()> todo;
protected:
    virtual void run() override{
        todo();
    }
};

int main(int argc,char *argv[])
{
    QApplication a(argc,argv);

    QObject mainObj;

    Thread thread;
    thread.todo = [&](){
        //QObject *obj = new QObject(&mainObj); //"QObject: Cannot create children for a parent that is in a different thread." ! Of course!

        // So we try this
        QObject *obj = new QObject;

        qDebug()<<obj->thread();
        obj->moveToThread(mainObj.thread());
        qDebug()<<obj->thread(); //Check that the Thread affinity change is done

        QMetaObject::invokeMethod(&mainObj,obj](){
            obj->setParent(&mainObj);
        });

        QObject::connect(obj,&QObject::destroyed,[](){ //Parent mecanism is OK
            qDebug()<<"Child destroyed";
        });
    };
    thread.start();
    thread.wait();

    QTimer::singleShot(0,[](){
        qApp->quit();
    });
    return a.exec(); //Add an event loop for the connect (queued) from the thread -> setParent() triggers a SendEvent() in the main thread where the two object now live
}
,

即使我的操作不是典型的,它仍然有效。 简单的错误或至少未解决的情况。

胡说八道。

Qt中没有错误,该错误在您的代码中。 obj是从Thread :: run线程(即您的lambda)实例化的。 然后将其移出此线程(Thread :: run线程),然后就无法再与其直接通信,因为线程安全... QObject是可重入的,不是线程安全的。 继续与该指针通信的唯一方法是通过同步,即使用排队的事件/连接,例如QMetaObject :: invokeMethod,QTimer :: singleShot或发出连接到obj插槽的信号。

这是可以接受的解决方法

不。这是正确做事的唯一方法。