Qt多线程中的信号与槽

1. Qt对象的依附性和事务循环

  QThread继承自QObject,自然拥有发射信号/定义槽函数的能力。QThread默认声明了以下几个关键信号(信号只能声明不能定义):
  (1) 线程开始运行时发射的信号

void started()

  (2) 线程完成运行时发射的信号

void finished()

  (3) 线程被异常终止时发射的信号

void terminated()

  多线程中的信号与槽之间的关系,是Qt编程中一个相对复杂的内容。先看下面例子:

//TestThread.h声明了线程类
#ifndef TESTTHREAD_H
#define TESTTHREAD_H

#include <QThread>
#include <QObject>
#include <QDebug>

//在线程类TestThread中自定义信号和槽函数
class TestThread : public QThread
{
    Q_OBJECT

protected:
    void run(); //线程执行函数

public:
    explicit TestThread(QObject *parent = 0);

signals:
    void TestThread_Signal();   //自定义的信号

public slots:
    void TestThread_Slot();     //对应TestThread_Signal()信号的槽函数
};

#endif // TESTTHREAD_H

//TestThread.cpp
#include "TestThread.h"
#include <QDebug>

TestThread::TestThread(QObject *parent) :
    QThread(parent)
{
    //连接信号与槽
    connect(this, SIGNAL(TestThread_Signal()), this, SLOT(TestThread_Slot()));
}

//子线程执行函数
void TestThread::run()
{
    //打印新线程的ID值
    qDebug() << "void TestThread::run(): tid = " << currentThreadId();

    //发射自定义信号
    emit TestThread_Signal();
}

void TestThread::TestThread_Slot()
{
    //打印此函数运行时的所在线程的ID值
    qDebug() << "TestThread::TestThread_Slot(): tid = " << currentThreadId();
}

//main.c
#include <QtCore/QCoreApplication>
#include "TestThread.h"
#include "MyObject.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;

    //开启线程执行函数,run()得到执行
    t.start();

    return a.exec();
}

  运行:
这里写图片描述

  上面代码的运行结果:
  槽函数执行时的所在线程和信号发送操作的所在线程并不是同一个,前者位于main线程中,后者位于子线程中。

  由此可以引申两个问题:
  (1) 二者同属于子线程类,程序运行时发送信号操作在子线程完成,对应的槽函数却是在main线程执行,究其原因,得从Qt对象的依附性说起。
  在Qt编程中,默认情况下,对象依附于创建自身的线程,例如上面代码中TestThread对象t它是在main()函数中创建的,那么t依附于主线程,而槽函数在其所依附的线程中被调用执行,因此,槽函数TestThread_Slot()是在main线程中执行。

  要想让TestThread_Slot()函数运行在main()创建的子线程中,可以使用moveToThread()函数更改TestThread对象所依附的线程:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;

    t.moveToThread(&t);     //更改TestThread对象的依附性到t线程

    t.start();

    return a.exec();
}

  运行:
这里写图片描述

  需要强调,这样的做法在实际工程中并不采取。因为QThread应该被看做是操作系统线程的接口或控制点,而不应该包含需要在新线程中运行的业务逻辑代码。显然,修改TestThread对象所依附的线程恰恰是打算在线程类对象中实现业务逻辑代码。

  (2) 若要对上面的代码的运行结果做个转换:槽函数运行时所依赖的是子线程,信号发送操作是在main()线程中完成,何以实现?

//定义一个存放槽函数的类MyObject,它继承自QObject
//MyObject.h
#ifndef MYOBJECT_H
#define MYOBJECT_H

#include <QObject>
#include <QThread>
#include <QDebug>

class MyObject : public QObject
{
    Q_OBJECT
public:
    explicit MyObject(QObject *parent = 0);

public slots:           //定义两个槽函数
    void getStarted();
    void MyObjectSlot();
};

#endif // MYOBJECT_H

//MyObject.cpp
#include "MyObject.h"

MyObject::MyObject(QObject *parent) :
    QObject(parent)
{
}

void MyObject::getStarted()
{
    qDebug() << "MyObject::getStarted(): tid = " << QThread::currentThreadId();
}

void MyObject::MyObjectSlot()
{
    qDebug() << "MyObject::MyObjectSlot(): tid = " << QThread::currentThreadId();
}

在main函数中,定义线程对象和MyObject对象,并将MyObject的依附性改为子线程
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;
    MyObject m;

    //m.moveToThread(&t);
    QObject::connect(&t, SIGNAL(TestThread_Signal()), &m, SLOT(MyObjectSlot()));
    t.start();      //run()函数的实现体和上面的一样,会发射TestThread_Signal()信号。该信号在这里还增加连接了MyObjectSlot的槽,所以除了会触发
                    //TestThread_Slot()函数外还会触发MyObjectSlot()

    return a.exec();
}

  运行:
这里写图片描述

  TestThread::TestThread_Slot()得到调用,但是MyObject::MyObjectSlot()却没有,这就要引入事件循环的概念。当发送信号操作所在的线程和槽函数执行时所在的线程是不同线程时,就需要事件循环的支持。如main函数中的”a.exec()”函数的调用也是开启事件循环,QThread线程类同样提供了exec()函数用于开启线程的事件循环,发送信号和槽函数位于不同的线程时,只有槽函数所在线程开启了事件循环,它才能在对应信号发射后被调用。无论事件循环是否开启,信号发送后会直接进入槽函数所依附的线程的事件队列,然而,只有开启了事件循环,对应的从函数才会在线程中得到调用。

这里写图片描述

  从上图可知,事件循环是一个无止尽循环,事件循环结束之前,exec()函数后的语句无法得到执行。问线程如何正常结束?这就需要quit()或者载exit()函数用于结束事件循环。

  在TestThread::run()函数中增加exec()函数的调用:

void TestThread::run()
{
    qDebug() << "void TestThread::run(): tid = " << currentThreadId();

    emit TestThread_Signal();

    exec(); //开启子线程的事件循环
    qDebug() << "hello";
}

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "main: tid = " << QThread::currentThreadId();

    TestThread t;
    MyObject m;


    m.moveToThread(&t);

    QObject::connect(&t, SIGNAL(TestThread_Signal()), &m, SLOT(MyObjectSlot()));

    t.start();          //开启线程执行函数
    t.wait(8 * 1000);   //休眠等待8s,确保事件队列中的槽函数得到执行后,再让子线程结束事件循环
    t.quit();

    return a.exec();
}

  运行:
这里写图片描述

  需要注意的是,当信号的发送与对象的槽函数的执行在不同线程中时,可能会产生临界资源的竞争问题。

2. 信号与槽的连接方式

  信号与槽的连接函数的原型为:

bool QObject::connect (const QObject * sender, 
                        const char * signal, 
                        const QObject * receiver,
                        const char * method,
                        Qt::ConnectionType type = Qt::AutoConnection)

  其中第5个参数决定信号与槽的连接方式,用于决定槽函数被调用时的相关行为。

Qt::AutoConnection              默认连接
Qt::DirectConnection            槽函数立即调用
Qt::BlockingQueuedConnection    同步调用
Qt::QueuedConnection            异步调用
Qt::UniqueConnection            单一连接

  (1) Qt::DirectConnection(立即调用)
  直接在发送信号的线程中调用槽函数(发送信号和槽函数位于同一线程),等价于槽函数的实时调用。

  (2) Qt::QueuedConnection(异步调用)
  信号发送至目标线程的事件队列(发送信号和槽函数位于不同线程),交由目标线程处理,当前线程继续向下执行。

  (3) Qt::BlockingQueuedConnection(同步调用)
  信号发送至目标线程的事件队列,由牧宝想线程处理。当前线程阻塞等待槽函数的返回,之后向下执行。

  (4) Qt::AutoConnection(默认连接)
  当发送信号线程=槽函数线程时,效果等价于Qt::DirectConnection;
  当发送信号线程!=槽函数线程时,效果等价于Qt::QueuedConnection。
  Qt::AutoConnection是connect()函数第5个参数的默认值,也是实际开发中字常用的连接方式。

  (5) Qt::UniqueConnection(单一连接)
  功能和AutoConnection相同,同样能自动确定连接类型,但是加了限制:同一个信号和同一个槽函数之间只能有一个连接。

展开阅读全文

没有更多推荐了,返回首页