c++multithreadingqtqmlqquickwidget

Qt Unable to move target to thread


I'm facing a strange bug in my Qt 5.7 (on Windows 10) application and the usual culprits for this kind of behaviour are nowhere to be found:

The full error message is

QObject::moveToThread: Current thread (0x2afcca68) is not the object's thread (0x34f4acc8). Cannot move to target thread (0x34f4adc8)

QObject::setParent: Cannot set parent, new parent is in a different thread

and here is also my code:

main.cpp

#include <QApplication>
#include <QQuickItem>
#include "CustomQuickWidget.h"

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QApplication app(argc, argv);

    const QUrl source = QUrl(QLatin1String("qrc:/main"));
    CustomQuickWidget widget(source);

    return app.exec();
}

main (alias for main.qml):

// You can put any random QML content in this case really as long as it doesn't create a window since the CustomQuickWidget does that.
Rectangle {
    id: window
    visible: true
    width: 600
    height: 480
}

CustomQuickWidget.cpp

#include "CustomQuickWidget.h"
#include <QQuickItem>

CustomQuickWidget::CustomQuickWidget(const QUrl &source, QWidget *parent) : QQuickWidget(source, parent) {
    // Setup the recognizer
    this->airWheelRecognizer = new QAirWheelGestureRecognizer();
    this->airWheelType = QGestureRecognizer::registerRecognizer(airWheelRecognizer);
    // and turn on grabbing for all the supported gestures
    grabGesture(airWheelType);
    grabGesture(Qt::SwipeGesture);
    grabGesture(Qt::TapGesture);

    // Create thread and device worker
    this->deviceThread = new QThread(this);
    this->deviceWorker = new DeviceMapper(this, Q_NULLPTR); // NOTE: this here is NOT for parent. The constructor's signature for this class is: DeviceMapper(QObject* receiver, QList<Qt::GestureType>* gestureIDs, QObject* parent = Q_NULLPTR)
    this->deviceWorker->init();

    // Create timer that will trigger the data retrieval slot upon timeout
    this->timer = new QTimer();
    this->timer->setTimerType(Qt::PreciseTimer);
    this->timer->setInterval(5);

    // Move timer and device mapper to other thread
    this->timer->moveToThread(this->deviceThread);
    this->deviceWorker->moveToThread(this->deviceThread); // FIXME For unknown reason: QObject::moveToThread: Current thread (...) is not the object's thread. Cannot move to target thread

    // Connect widget, timer and device mapper
    createConnections();

    // Run thread
    this->deviceThread->start();

    // Connect device and start data retrieval
    QTimer::singleShot(0, this->deviceWorker, &(this->deviceWorker->slotToggleConnection));
    QTimer::singleShot(0, this->deviceWorker, &(this->deviceWorker->slotToggleRun));

    this->show();
}

CustomQuickWidget::~CustomQuickWidget()
{
    if (this->deviceThread) {
        this->deviceThread->quit();
        this->deviceThread->wait();
    }
}

void CustomQuickWidget::createConnections()
{
    connect(this->timer, SIGNAL(timeout()),
            this->deviceWorker, SLOT(slotRetrieveData()));

    connect(this->deviceThread, SIGNAL(started()),
            this->timer, SLOT(start()));
    connect(this->deviceThread, SIGNAL(finished()),
            this->deviceWorker, SLOT(deleteLater()));
    connect(this->deviceThread, SIGNAL(finished()),
            this->deviceThread, SLOT(deleteLater()));
}

bool CustomQuickWidget::event(QEvent* event) {
    if (event->type() == QEvent::Gesture) { 
        bool res = gestureEvent(static_cast<QGestureEvent*>(event)); // Not important so not included as code here
        return res;
    }

    return QWidget::event(event);
}

As you can see I have a typical worker-thread-thing going on here. I've made sure that my worker (here DeviceMapper) doesn't have a parent. It is also instantiated inside my widget (where the QThread is also created) but moved to the thread along with a timer.

Now beside the obvious issue here that is in the title I have to mention the following:

The only difference between my other application and this one is the usage of QQuickWidget (instead of QWidget) and QML. I'm quite new to QML and this is also my first QQuickWidget so I might be missing some obvious setting that needs to be "activated".

I've also added

cout << this->deviceWorker->thread()->currentThreadId() << endl;
cout << this->thread()->currentThreadId() << endl;

right before this->deviceWorker->moveToThread(this->deviceThread); and I got

0x18b0
0x18b0

which means that before the moveToThread(...) my object belongs to the same thread where the QThread is instantiated. Printing the thread ID after the moveToThread(...) returns the same result but this is expected due to the failure to properly move the object to the other thread.


UPDATE:

The error message appears ONLY when building in release mode however no matter the type of build I have the bug is still present.


Solution

  • I have managed to solve my problem by pinpointing WHEN it's happening.

    At the end of last week the application I was writing started working all of a sudden so even though it bothered me why all that happened before that I let it be. I have neither changed the library's code (except for a couple of comments in my code which obviously cannot affect the code itself) nor the C++ code of my QML application. All I have changed was my QML but in a way that didn't actually relate to the C++ code underneath. The only thing I changed was the build type. However I didn't actually notice that last week.

    Yesterday I started working on a new project. And right after doing the very first run I got the same issue. It drove me nuts. So I started analyzing my code (@Kuba Ober, sorry mate, but posting the complete code or even a small chunk of the library is not possible otherwise I would have done it (even though it's a couple of hundreds of lines of actual code (excluding stuff such as comments and empty lines)). I checked and double-checked the parent-child relationships but couldn't find anything that could give me even a small hint when and why this is happening. I have also analyzed the stack to the best of my abilities but all in vain.

    Then it struck me...I've mentioned above that my previous project started working all of a sudden after changing its build type. And indeed this was the source of all evil in my situation. The way I add my library to my projects (excluding the initial one which along with the library is part of the same subdir project) is by creating a folder in the root directory of my new project called libs and copying the related stuff to it. Now after I finished working on my library and did some testing I obviously decided to switch to release build. However I copied a library build in release mode to a project build in debug mode. So after a couple of rebuilds and copying the library here and there I found out that mixing the build types of the application that uses the library and the library itself lead to this issue.

    I know that mixing build types is a bad idea and I don't that normally but this time it just slipped my mind and was a total accident. I don't know what is happening internally when both the application with X build type and the library with Y build type are mixed but the result in my case was the error I have posted in this thread.

    Thanks for all the help. I learned a lot through your comments! Even though debugging was not necessary in my case you have my gratitude. :)