Qt 无法将目标移动到线程

Qt Unable to move target to thread

我的 Qt 5.7(在 Windows 10)应用程序中遇到了一个奇怪的错误,并且找不到造成这种行为的常见罪魁祸首:

完整的错误信息是

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

这也是我的代码:

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();
}

mainmain.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);
}

如您所见,我这里有一个典型的 worker-thread-thing。我已经确定我的员工(此处 DeviceMapper)没有 parent。它也在我的小部件中实例化(QThread 也被创建)但与计时器一起移动到线程。

现在除了标题中的明显问题外,我还必须提及以下内容:

我的另一个应用程序和这个应用程序之间的唯一区别是 QQuickWidget(而不是 QWidget)和 QML 的用法。我对 QML 很陌生,这也是我的第一个 QQuickWidget,所以我可能会遗漏一些需要 "activated".

的明显设置

我也添加了

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

就在 this->deviceWorker->moveToThread(this->deviceThread); 之前,我得到了

0x18b0
0x18b0

这意味着在 moveToThread(...) 之前,我的 object 属于实例化 QThread 的同一个线程。在 moveToThread(...) returns 之后打印线程 ID 的结果相同,但这是预期的,因为未能将 object 正确移动到另一个线程。


更新:

只有在发布模式下构建时才会出现错误消息,但是无论构建类型如何,我的错误仍然存​​在。

我已经设法通过查明问题发生的时间来解决我的问题。

上周结束时,我正在编写的应用程序突然开始工作,所以尽管我很困扰为什么之前发生的所有事情,但我还是顺其自然。我既没有更改库的代码(除了我的代码中的一些注释显然不会影响代码本身),也没有更改我的 QML 应用程序的 C++ 代码。我所更改的只是我的 QML,但实际上与下面的 C++ 代码无关。我唯一改变的是构建类型。然而上周我并没有真正注意到这一点。

昨天我开始了一个新项目。在完成第一个 运行 之后,我遇到了同样的问题。这让我抓狂。所以我开始分析我的代码(@Kuba Ober,对不起,伙计,但是发布完整的代码甚至库的一小部分是不可能的,否则我会这样做(即使它有几百行实际代码(不包括评论和空行之类的东西))。我检查并仔细检查了父子关系,但找不到任何可以给我哪怕是一个小提示的东西,什么时候以及为什么会发生这种情况。我还分析了堆栈以尽我所能,但都是徒劳。

然后让我震惊...我在上面提到过,我以前的项目在更改其 构建类型 后突然开始工作。在我的处境中,这确实是万恶之源。我将我的库添加到我的项目中的方法(不包括与库一起属于同一个 subdir 项目的初始库)是在我的新项目的根目录中创建一个名为 libs 的文件夹并将相关内容复制到其中。现在,在我完成我的库并进行一些测试后,我显然决定切换到 release build。但是,我将 release 模式下的 库构建复制到 debug 模式 下的项目构建。因此,在多次重建并在各处复制库之后,我发现混合使用该库的应用程序的构建类型和库本身会导致此问题。

我知道混合构建类型是一个坏主意,而且我通常不会那样做,但这次我只是忘记了,完全是个意外。当具有 X 构建类型的应用程序和具有 Y 构建类型的库混合使用时,我不知道内部发生了什么,但我的结果是我在此线程中发布的错误。

感谢大家的帮助。我从你的评论中学到了很多东西!尽管在我的情况下不需要调试,但我很感激你。 :)