跨线程的Qt信令,一个是GUI线程?

时间:2010-01-18 13:03:43

标签: user-interface qt signals-slots qthread

使用moveToThread在Qt中将对象从一个线程移动到另一个线程是什么意思?甚至在使用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);
 }

5 个答案:

答案 0 :(得分:37)

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

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

修改:在回复您的评论时,请查看MandelbrotWidget Class Implementation标题下的Mandelbrot示例:

  

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

我认为这有点过时了,这里是valid meta types。由于线程上的信号和插槽使用排队连接,因此在大多数情况下,您不必执行moveToThread调用。

编辑: 我将尝试用类似的例子解释事情:

mythread.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

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

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

点击按钮后的示例输出:

Thread id 1088110320
Thread id inside run 1093176208
String address inside run 0x41288350
Thread id inside setText 1088110320
String address inside setText 0x974af58

如您所见,运行线程与主GUI线程不同。此外,即使您将const引用传递给QString,因为它跨越线程边界,它会复制它。 我强烈鼓励您阅读Threads and QObject

答案 1 :(得分:8)

  1. QThread::start()方法创建线程并调用run()实现。如果您想在线程上处理事件或收到信号,您必须在QThread::exec()实施中调用。您不应该明确地致电run(),也不应该在run()之外致电exec()

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

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

答案 2 :(得分:3)

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

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

在文章中,我认为延迟是由于呼叫是直接的,即根本没有在后台处理(但我只是略读了文本)。

答案 3 :(得分: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();
 }

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

答案 4 :(得分:0)

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