29

在 Qt 中使用 moveToThread 将对象从一个线程移动到另一个线程是什么意思?甚至在使用 moveToThread 之前,一切似乎都正常工作,它将对象从一个线程(GUI 线程)移动到另一个线程(工作)并且 Qt:connect 调用对象上的适当插槽。

由于对象所在的位置、GUI 线程或工作线程有什么区别吗?

编辑:我做了一个小程序,但我不明白 QThread 如何与 Signal 和 slot 函数一起工作,如果你能用示例解释 moveToThread 的用途,我将不胜感激

#include <QtGui/QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QString>
#include "mythread.h"
//GUI calls a thread to do some job and sub update the text box once it is done
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;
    QHBoxLayout * pH = new QHBoxLayout(&w);
    QPushButton * pushButton = new QPushButton("asdad");
    QLineEdit * lineEdit = new QLineEdit("AAA");
    pH->addWidget(pushButton);
    pH->addWidget(lineEdit);
    w.setLayout(pH);
    w.show();
    MyThread thread;
    qDebug("Thread id %d",(int)QThread::currentThreadId());
    QObject::connect(pushButton,SIGNAL(clicked()),&thread,SLOT(callRun())) ;
    QObject::connect(&thread,SIGNAL(signalGUI(QString)),lineEdit,SLOT(setText(QString)));
    return a.exec();
}

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
public slots:
    void callRun();
    void run();
 signals:
    void signalGUI(QString);
private:
    QMutex mutex;

};

#endif // MYTHREAD_H


#include "mythread.h"
#include <QDebug>
#include <QString>
#include <QMutexLocker>

MyThread::MyThread()
{
}
 void MyThread::callRun()
 {

     qDebug("in thread");
    if(!isRunning())
     {
        this->start(LowestPriority);
        exec();
    }
    else
    {
        run();
    }

 }
 void MyThread::run()
 {
     QMutexLocker fn_scope(&mutex);
     static int a = 0;
    ++a;
     qDebug("Thread id inside run %d",(int)QThread::currentThreadId());
     this->sleep(3);
     static QString number;
     QString temp;
     number += temp.setNum(a);
     emit signalGUI(number);
 }
4

5 回答 5

38

看看Signals and slots across threads。如果您总是使用信号和槽与工作线程通信,Qt 会在需要时为您处理 moveToThread,并且您使用了正确的连接。

编辑:我猜这篇文章的作者看到了他的问题,因为他在实际创建线程之前在构造函数中调用 start 。换句话说,不要盲目相信第三方代码。

编辑:作为对您的评论的回应,请查看标题下的Mandelbrot示例:MandelbrotWidget Class Implementation

对于排队连接,Qt 必须存储传递给信号的参数的副本,以便以后可以将它们传递给插槽。Qt 知道如何复制许多 C++ 和 Qt 类型,但 QImage 不是其中之一。因此,我们必须先调用模板函数 qRegisterMetaType(),然后才能使用 QImage 作为队列连接中的参数。

我相信这有点过时了,这里是有效的元类型。由于跨线程的信号和槽使用排队连接,因此在大多数情况下您不必执行 moveToThread 调用。

编辑:我将尝试用一个类似的例子来解释:

我的线程.h:

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>

class MyThread : public QThread
{
   Q_OBJECT

protected:
   virtual void run();

signals:
   void signalGUI(QString);
};

#endif // MYTHREAD_H

我的线程.cpp:

#include "mythread.h"
#include <QString>

void MyThread::run()
{
   qDebug("Thread id inside run %d",(int)QThread::currentThreadId());
   static int run = 0;
   QString temp = QString("Run: %1").arg(run++);
   qDebug("String address inside run %p", &temp);
   emit signalGUI(temp);
}

mylineedit.h

#ifndef MYLINEEDIT_H
#define MYLINEEDIT_H

#include <QLineEdit>

class MyLineEdit : public QLineEdit
{
Q_OBJECT
public:
    explicit MyLineEdit(QWidget *parent = 0);

public slots:
    void setText(const QString &string);

};

#endif // MYLINEEDIT_H

mylineedit.cpp

#include "mylineedit.h"
#include <QThread>

MyLineEdit::MyLineEdit(QWidget *parent) :
    QLineEdit(parent)
{
}

void MyLineEdit::setText(const QString &string)
{
   qDebug("Thread id inside setText %d",(int)QThread::currentThreadId());
   qDebug("String address inside setText %p\n", &string);
   QLineEdit::setText(string);
}

主.cpp:

#include <QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include "mythread.h"
#include "mylineedit.h"

//GUI calls a thread to do some job and sub update the text box once it is done
int main(int argc, char *argv[])
{
   QApplication a(argc, argv);
   QWidget w;
   QHBoxLayout * pH = new QHBoxLayout(&w);
   QPushButton * pushButton = new QPushButton("Run Thread", &w);
   MyLineEdit * lineEdit = new MyLineEdit(&w);

   pH->addWidget(pushButton);
   pH->addWidget(lineEdit);
   w.show();

   MyThread thread;
   qDebug("Thread id %d",(int)QThread::currentThreadId());
   QObject::connect(pushButton,SIGNAL(clicked()),&thread,SLOT(start())) ;
   QObject::connect(&thread,SIGNAL(signalGUI(const QString&)),lineEdit,SLOT(setText(const QString&)));
   return a.exec();
}

单击按钮后的示例输出:

线程 ID 1088110320
线程 id 内部运行 1093176208
运行中的字符串地址 0x41288350
setText 1088110320 中的线程 id
setText 0x974af58 内的字符串地址

如您所见,运行线程与主 GUI 线程不同。此外,即使您将 const 引用传递给 QString,由于它跨越线程边界,它也会复制它。我强烈建议您阅读Threads 和 QObject

于 2010-01-18T17:47:44.167 回答
8
  1. QThread::start()方法创建线程并调用您的run()实现。如果您想在线程上处理事件或接收到的信号,您必须QThread::exec() 您的run()实现中调用。你永远不应该run()显式调用,也不应该exec()run().

  2. 仅当插槽连接到连接类型不是 的信号时,所有者线程才会有所不同Qt::DirectConnection。然后 Qt 将确保插槽在所有者线程上运行,但为此所有者线程必须运行带有QThread::exec(). 在这种情况下,调用myObj.moveToThread(myThread)将确保myObj槽在线程上运行myThread

  3. 线程对象属于创建它的线程,而不是它管理的线程(以及 run 方法将运行的位置)。因此,当您将信号连接到线程对象的插槽时,该插槽将在创建线程对象的线程中运行,除非您调用moveToThread().

于 2010-01-22T10:40:26.083 回答
3

在线程之间移动对象时,您决定它属于哪个事件循环。在线程内建立连接时,信号代码直接调用每个插槽(必须等待它们完成)。跨线程边界发出信号将信号调用放在事件循环上,让插槽的线程在准备好时调用插槽。

在线程之间进行直接调用需要您确保您的函数是可重入的。您还必须确保使用互斥锁或信号量来保护您的数据,同时避免竞争条件。

在文章中,我猜测延迟是由于调用是直接的,即根本没有在后台处理(但我只是略读了文本)。

于 2010-01-18T14:45:07.373 回答
3
#include <QtGui/QApplication>
#include <QPushButton>
#include <QHBoxLayout>
#include <QLineEdit>
#include <QString>
#include "mythread.h"
//GUI calls a thread to do some job and sub update the text box once it is done
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QWidget w;
    QHBoxLayout * pH = new QHBoxLayout(&w);
    QPushButton * pushButton = new QPushButton("asdad");
    QLineEdit * lineEdit = new QLineEdit("AAA");
    pH->addWidget(pushButton);
    pH->addWidget(lineEdit);
    w.setLayout(pH);
    w.show();
    MyThread thread;
    thread.moveToThread(&thread);
    thread.start();
    qDebug("Thread id %d",(int)QThread::currentThreadId());
    QObject::connect(pushButton,SIGNAL(clicked()),&thread,SLOT(callRun()),Qt::QueuedConnection) ;
    QObject::connect(&thread,SIGNAL(signalGUI(QString)),lineEdit,SLOT(setText(QString)),Qt::DirectConnection);
    return a.exec();
}

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>
#include <QMutex>

class MyThread : public QThread
{
    Q_OBJECT
public:
    MyThread();
public slots:
    void callRun();
    void run();
 signals:
    void signalGUI(QString);
private:
    QMutex mutex;

};

#endif // MYTHREAD_H
#include "mythread.h"
#include <QDebug>
#include <QString>
#include <QMutexLocker>

MyThread::MyThread()
{
}
 void MyThread::callRun()
 {
     QMutexLocker fn_scope(&mutex);
     static int a = 0;
    ++a;
     qDebug("Thread id inside run %d",(int)QThread::currentThreadId());
     this->sleep(3);
     static QString number;
     QString temp;
     number += temp.setNum(a);
     emit signalGUI(number);

 }
 void MyThread::run()
 {
    exec();
 }

新的线程对象被创建并且线程对象被移动到同一个线程。信号现在跨线程,连接类型都是队列,它按预期工作。

于 2010-01-20T14:46:54.770 回答
0

某些对象只能在所有者线程上使用。例如,如果您在一个线程中创建和套接字对象,并且您想在另一个线程中发送和接收数据,这是不可能的。因此,一种解决方案是将对象从一个线程移动到另一个线程并对其进行操作。

于 2010-01-19T17:10:57.997 回答