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

Create a QObject in another thread and retrieve it to the current thread = ASSERT failure in Debug on msvc16

我只是想在另一个线程中创建的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 在调试中启动此代码:

所以我调试了 qt 库,问题来自 setParent() 执行 sendEvent() 而不检查线程关联。这 2 个对象位于同一个线程中,但调用的 setParent() 是从另一个线程完成的。尽管我的操作不典型,但它仍然有效..这是一个简单的错误或者至少是一个未处理的案例。

根据调用线程,它应该执行 postevent()

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

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

此调用是通过排队连接自动完成的,并最终在正确的线程中执行。当然我们必须在主线程中启动事件循环来检索这个排队的事件。

这是一个可接受的解决方法

#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, [&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
}

Even though my operation isn't typical, it's still valid.. It's a simple bug or at least an unhandled case.

纯粹的废话。

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

This is an acceptable workaround

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