【问题标题】:Qt Unable to move target to threadQt无法将目标移动到线程
【发布时间】:2016-08-09 11:57:46
【问题描述】:

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

  • 被移动的对象有一个父对象 - 肯定不是这样
  • 试图将对象拉到线程而不是推送它 - 这是错误的原因,但我不知道它来自哪里

完整的错误信息是

QObject::moveToThread: 当前线程 (0x2afcca68) 不是对象的 线程(0x34f4acc8)。无法移动到目标线程 (0x34f4adc8)

QObject::setParent: 无法设置父级,新父级在不同的 线程

这也是我的代码:

ma​​in.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();
}

ma​​inma​​in.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);
}

如您所见,我这里有一个典型的工作线程。我已经确定我的工人(这里是DeviceMapper)没有父母。它也在我的小部件(QThread 也被创建)内部实例化,但与计时器一起移动到线程。

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

  • 调用this-&gt;timer-&gt;moveToThread(this-&gt;deviceThread);时没有这样的错误
  • 这个完全相同的代码在另一个项目中没有任何问题,这是一个子目录项目 - 一个子项目创建共享库(我也在这个项目中使用),另一个 - 一个使用该库的应用程序.

我的另一个应用程序和这个应用程序之间的唯一区别是QQuickWidget(而不是QWidget)和QML 的用法。我对QML 很陌生,这也是我的第一个QQuickWidget,所以我可能会遗漏一些需要“激活”的明显设置。

我也加了

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

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

0x18b0
0x18b0

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


更新:

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

【问题讨论】:

  • QThread::currentThreadId() 是一个静态函数,它返回当前执行代码的线程,而不是对象所在的线程。只需调试QObject::thread() 即可找出对象所在的线程。
  • 通过qInstallMessageHandler 安装自定义消息处理程序并在其中设置断点,然后等到您从 Qt 收到警告。然后在堆栈中查找以检查哪个 QObject 实际发出它。它可能是deviceWorker 的某个子对象。
  • 顺便说一句,我知道很多 Qt 的过时文档都在使用它,这很不幸,但是所有的手册 new/delete 通常都不需要,让你容易发生意外,并且不是好的/现代的 C++ 风格...... Qt 也不是,但它最近终于做出了适当的努力,只要所有过时的文档和教程都能赶上。我建议更改您不需要超过声明它的范围的任何对象,以具有自动/按值存储持续时间。这可能会或可能不会影响这一点 - 但只是很好的风格,可能会防止各种其他可能的头痛
  • @rbaleksandar both my timer and the deviceWorker need to be dynamically allocated. Otherwise once the constructor is done they will go out of scope and that's that. 但这不是真的!他们是班级成员。唯一可以在构造函数末尾超出范围的是在其主体中声明的局部变量。如果您为这些成员提供自动存储持续时间,那么它们的生存时间与它们的包含对象一样长。
  • 您要么从错误的线程调用DeviceWorker 的非线程安全方法,要么DeviceWorker 拥有一些不是其子对象的对象。无论哪种方式,您绝对必须发布一个完整的示例,并且它应该在一个文件中。在这样的测试用例中拥有单独的头文件是完全没有意义的。您还应该继续删除代码,直到在错误消失的情况下无法删除任何代码。将所有内容放入以#include "main.moc" 结尾的main.cpp。参见例如this answer 一个想法。

标签: c++ multithreading qt qml qquickwidget


【解决方案1】:

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

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

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

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

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

感谢所有帮助。我通过您的 cmets 学到了很多东西!尽管在我的情况下不需要调试,但我很感激你。 :)

【讨论】:

  • 因此,即使您编译的应用程序和库的构建类型不匹配——但是相反——你得到了相同的结果吗?这可以解释,但我担心这可能同样是由于不同的优化交替掩盖/显示 UB 的结果。我看到了一些让人想起你所说的东西,但那是因为我无意中用错误的编译器编译了我的程序 (lol) 并将它与由正确编译器编译的库链接。我直觉理解的那个。来自同一个编译器的不兼容的构建模式,不是那么多。但我可能错了……我们的构建专家怎么看?
  • 是的,唯一可行的情况是它们的构建类型匹配 dll(调试中)+ 应用程序(调试中)和 dll(发布中)+ 应用程序(发布中)= 没有问题.至于确切的为什么问题正在发生,我写道我没有最微弱的想法。由于我是一个好奇的人,我很想知道这一点,但鉴于我无法提供库的代码,我看不出关于这个问题还有什么可以说的。顺便说一句,最初的 subdir 项目使用相同的构建类型构建应用程序和共享库,这就是问题没有更早出现的原因。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多