Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

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



  • Hi everyone,
    I am just trying to set a parent on a QObject created in another thread (the object is of course moved to the parent thread before), that's all !

    #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
    }
    

    This example is working fine in release, but if you try to launch this code in debug with mscv16:

    799a3782-874c-4339-bfde-dff6c420abf0-image.png
    Maybe the call of obj->setParent(&mainObj) doesn't like that mainObj is not in the thread calling the method ..?


  • Lifetime Qt Champion

    You're accessing a QObject and doing a lot of stuff with it inside another thread - that this will crash sooner or later should be clear.


  • Lifetime Qt Champion

    You can't set a parent which is not in the same thread.



  • @Christian-Ehrlicher Of course ! This is not what I am doing here !
    The objects have the same thread affinity when I do the setParent()
    You can see a commented line of what I am not doing :-D
    50d0b760-71c1-43aa-86ba-fe57f727051c-image.png


  • Lifetime Qt Champion

    But you're calling the QObject function from another thread which creates an event in the thread where the object does not life in and therefore the assert



  • @Christian-Ehrlicher That is what I supposed "Maybe the call of obj->setParent(&mainObj) doesn't like that mainObj is not in the thread calling the method ..?"
    So obj1->setParent(obj2) triggers a sendEvent() ? No mention of that in documentation !!
    But if it does, it is weird to not check thread affinity and replace the sendEvent() by a postEvent() :-(

    Edit :
    @Christian-Ehrlicher This really ugly but indeed it worked, I had to force the execution of the setParent() in the main thread via a queued connection in the thread. And of course start the eventloop in the main thread

    #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);
    
        ///////////////////////////////////////////////
        // Change this flag to switch behaviours
        bool tryToSetParentInThread = true;
        ///////////////////////////////////////////////
    
        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
    
    
           QObject::connect(QThread::currentThread(), &QThread::finished, obj, [obj,&mainObj](){
               qDebug()<<"Most fucked up code I wrote";
               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
    }
    

  • Lifetime Qt Champion

    You're accessing a QObject and doing a lot of stuff with it inside another thread - that this will crash sooner or later should be clear.



  • So I debugged qt lib, the problem comes from setParent() doing a sendEvent() without checking the thread affinity. This is simply a bug.
    Depending on the calling thread it should make a postevent() instead
    cd79e018-a4ac-43bd-8878-9c53147f0f87-image.png
    I ve just replace obj->setParent(&mainObj); by

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

    The invokeMethod() execute setParent in the correct.


  • Lifetime Qt Champion

    @NiHoTleHot said in Create a QObject in another thread, retrieve it to the current thread and set a parent = ASSERT failure in Debug on msvc16:

    This is simply a bug.

    No, you're modifying an object which lives in another thread - it may work or it may crash.


Log in to reply