Qt 线程关联和 moveToThread 的问题

Posted

技术标签:

【中文标题】Qt 线程关联和 moveToThread 的问题【英文标题】:Issue with Qt thread affinity and moveToThread 【发布时间】:2013-06-06 10:48:47 【问题描述】:

我正在尝试使用 Qt 中的线程将一些工作委派给一个线程,但我无法让它工作。我有一个继承 QMainWindow 的类,它有一个启动线程来工作的成员对象。该对象以 QMainwindow 作为父对象。它包含并初始化另一个 QObject,m_poller,我想将其移至我创建的线程:

m_pollThread = new QThread;
m_poller->moveToThread(m_pollThread);
//Bunch of connection
m_pollThread->start();

我遵循了关于如何在 Qt 中管理线程而不对其进行子类化的指南(又名not doing it wrong),但我仍然在 VS 中收到以下消息:

QObject::moveToThread: 当前线程 (0x2dfa40) 不是对象的线程 (0x120cf5c0)。 无法移动到目标线程 (0x1209b520)

我发现following post 似乎可以处理相同的问题,但无法用答案修复我的代码。我觉得我实际上是在正确调用 moveToThread (因为我没有从另一个线程中调用它来“拉”一个对象),但显然我仍然遗漏了一些东西:正如消息提示的那样,似乎已经有多个线程,我对 moveToThread() 的调用似乎最终出现在错误的线程中(尽管我承认我对此完全陌生,并且可能完全错误地解决这个问题......)

那么我使用 Qt 线程的方式可能还有什么问题呢?

谢谢!

【问题讨论】:

能否请您显示创建 m_poller 对象的代码? 【参考方案1】:

你只能使用moveToThread以防万一

您的对象没有父对象(否则父对象将具有不同的线程亲和性) 您在对象的所有者线程上,因此您实际上将对象从当前线程“推送”到另一个线程

因此,您的错误消息表明您违反了第二种情况。您应该从创建对象的线程中调用moveToThread。 根据你的说法

这个对象有 QMainwindow 作为父对象。

因此 moveToThread 将再次不起作用。您应该从 m_poller 对象中删除父对象

【讨论】:

我已经阅读了 Qt 文档,m_poller 对象实际上没有父对象。相反,持有 m_poller 的对象有一个(但我的帖子很模棱两可......) 在这种情况下,您的代码似乎一切正常,您还可以检查 QThread::currentThread() == m_poller::thread() 以确保您以正确的方式使用 moveToThread .如果在使用 moveToThread 之前创建 m_poller 对象会发生什么?【参考方案2】:

我认为问题出在 m_poller 的初始化上,根据错误消息,它似乎分配给了与执行代码 sn-p 的线程不同的(第三个)线程。

此外,如果此代码被多次执行,它可能会在第一次运行,但随后会失败,因为 m_poller 不再属于正在执行的线程,而是 m_pollThread。

【讨论】:

m_poller 是一个自定义 QObject,它在同一个对象中初始化,然后创建线程并调用 moveToThread()(为了清楚起见,我将进行编辑)。 我已经编辑了我的答案,以反映即使 m_poller 最初是在“主”线程中创建的,这种情况仍然可能发生。 如何初始化“分配”m_poller 到另一个线程,它被初始化的那个线程? (注意这段代码的多次执行,确实非常好,尽管这发生在代码第一次执行时)。 不知道代码很难说...我认为您必须仔细跟踪代码的执行,以查看 m_poller 的线程亲和力何时发生变化。【参考方案3】:

您还可以通过从对象的所有者线程执行此操作将其移动到您的线程。

#include <thread>
#include <memory>
#include <condition_variable>
#include <QTimer>
#include <QThread>
#include <QApplication>


template <typename Func>
inline void runOnThread(QThread *qThread, Func &&func)

    QTimer *t = new QTimer();
    t->moveToThread(qThread);
    t->setSingleShot(true);
    QObject::connect(t, &QTimer::timeout, [=]()
    
        func();
        t->deleteLater();
    );
    QMetaObject::invokeMethod(t, "start", Qt::QueuedConnection, Q_ARG(int, 0));




void moveToThread(QObject *ptr, QThread *targetThrd=QThread::currentThread())

    std::mutex mt;
    std::condition_variable_any cv;
    runOnThread(ptr->thread(),[&]
    
        ptr->setParent(NULL);
        ptr->moveToThread(targetThrd);
        cv.notify_one();
    );
    cv.wait(mt);

你只需要打电话

moveToThread( m_poller, m_pollThread);

【讨论】:

【参考方案4】:

如果你通过信号和槽移动你的对象(你已经在一个线程中创建了你的 m_poller 并调用了一个信号并将它传递给另一个不在调用者线程中的对象的槽)确保使用 Qt::DirectConnection 类型为您的connect。这样,您的插槽将在调用者线程中执行,并且调用 moveToThread 在调用者线程中。

【讨论】:

以上是关于Qt 线程关联和 moveToThread 的问题的主要内容,如果未能解决你的问题,请参考以下文章

Qt线程—QThread的使用--run和movetoThread的用法

Qt 串口和线程的简单结合(通过子线程操作串口movetothread)

Qt 多线程使用moveToThread

qt中通过重写run方法创建线程与通过movetothread方法有啥区别

qt 线程接收线程 moveToThread 特性

QT多线程之---moveToThread用法