Qt线程亲和性和moveToThread的问题

6

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

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

我按照Qt中如何管理线程而不需要对其进行子类化的指南(又称正确姿势)操作,但是在VS中仍然收到以下消息:

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

我发现下面的帖子似乎处理了相同的问题,但无法使用答案修复我的代码。我觉得我实际上已经正确调用了moveToThread(就像我没有从另一个线程中调用它来“拉”对象一样),但显然我还是缺少了一些东西:正如消息所暗示的那样,似乎已经有多个线程,并且我的moveToThread()调用似乎进入了错误的线程(尽管我承认我完全是新手,可能完全搞错了...) 那么我在使用Qt线程的方式上可能仍然存在哪些问题?

谢谢!


请问您能否展示一下创建m_poller对象的代码? - Martin Hennings
4个回答

9

只有在以下情况下才能使用moveToThread

  • 您的对象没有父级(否则父级将具有不同的线程关联)
  • 您位于对象所有者线程上,因此您实际上是从当前线程“推动”对象到另一个线程

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

这个对象的父级是 QMainwindow。

所以moveToThread不起作用。您应该从 m_poller 对象中删除父级。


我已经阅读了Qt文档,m_poller对象实际上没有父对象。相反,持有m_poller的对象却有一个(但我的帖子表述不太清楚...) - JBL
在这种情况下,您的代码似乎一切正常,您还可以检查QThread :: currentThread()== m_poller :: thread(),以确保您正在正确使用moveToThread。如果您在使用moveToThread之前立即创建m_poller对象会发生什么? - spiritwolfform

2

您也可以通过将其从对象所有者线程执行来将其移动到您的线程。

#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);

1
非常聪明,而且有效!只需要一件事(至少对于C++17来说),在cv.wait()之前,需要有一个std::lock_guard lck(mt);,然后使用cv.wait(lck);而不是直接在互斥锁上等待(需要锁定互斥锁,否则会引发异常)。https://en.cppreference.com/w/cpp/thread/condition_variable_any/wait 也可以使用QWaitConditionQMutexLocker,以及几乎相同的语法,代替QMutex - Maxim Paperno

1
我认为问题在于m_poller的初始化,根据错误信息显示,它似乎被分配给了不同(第三个)线程,而非执行代码片段的线程。
此外,如果此代码被多次执行,它可能会在第一次成功后在随后的执行中失败,因为m_poller不再属于执行线程,而是归m_pollThread所有。

m_poller 是一个自定义的 QObject,它在创建线程并调用 moveToThread() 的同一对象中进行初始化(我会进行编辑以提高清晰度)。 - JBL
我已经编辑了我的答案以反映这个事实,即使m_poller最初是在“主”线程中创建的,这种情况仍可能发生。 - hmn
如何将初始化中的“assign” m_poller 分配给一个与其初始化所在的不同线程?(注意关于此代码的多次执行的记录,确实是一个很好的观点,尽管这发生在第一次执行代码时)。 - JBL
很难在不知道代码的情况下说...我认为你需要仔细跟踪代码的执行,以查看 m_poller 的线程亲和性何时发生变化。 - hmn

0

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接