七、信号与槽的连接方式
本节介绍信号与槽的五种连接方式:
Qt::DirectConnection(); //立即调用
Qt::QueuedConnection(); //异步调用
Qt::BlockingQueuedConnection(); //同步调用
Qt::AutoConnection(); //默认连接
Qt::UniqueConnection(); //单一连接
connection()函数的原型:
信号与槽的连接方式,决定了槽函数调用时候的相关行为。
知识回顾:每一个线程都有自己的事件队列,线程通过事件队列来接受信号,信号在事件循环中被处理。
编程示例:
代码和前面代码变化不大,主要是主函数。
void TestThread::run()
{
qDebug() << "void TestThread::run() -- begin tid" << QThread::currentThreadId();
for(int i=0; i<3; i++)
{
qDebug() << "void TestThread::run" << i;
sleep(1);
}
emit testSignal();
exec();
qDebug() << "void TestThread::run() -- end " ;
}
在主函数中,通过不同的方式对信号和槽进行连接,代码如下:
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include "TestThread.h"
#include "MyObject.h"
void direct_connection()
{
static TestThread t;
static MyObject m;
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::DirectConnection);
//直接忽略线程依附性,被调用的对象,直接在调用者的线程中被执行。注意目标线程和当先线程必须不同
qDebug() << " void direct_connection() ";
t.start();
t.wait(5 * 1000);
t.quit();
}
void queued_connection() //异步调用
{
static TestThread t;
static MyObject m;
qDebug() << " void queued_connection() ";
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::QueuedConnection);
//槽函数在主函数线程中被调用。 信号发送后,子线程run()先结束,进入主线程事件队列。
t.start();
t.wait(5 * 1000);
t.quit();
}
void blocking_queued_connection() //同步调用
{
static TestThread t;
static MyObject m;
qDebug() << " void blocking_queued_connection() ";
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::BlockingQueuedConnection);
//不改变槽函数线程依附性,只是等待子线程(槽函数)结束后,继续向下执行。注:目标(执行)线程和发送信号的线程必须是不同的
t.start();
t.wait(5 * 1000);
t.quit();
}
void auto_connection() //默认连接
{
static TestThread t;
static MyObject m;
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::AutoConnection);
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::AutoConnection);
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::AutoConnection);
//默认情况下,同一个信号可以多次连接到同一个槽函数,槽函数可以多次调用
t.start();
t.wait(5 * 1000);
t.quit();
}
void unique_connection() //单一连接
{
static TestThread t;
static MyObject m;
qDebug() << "void unique_connection() ";
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::UniqueConnection);
QObject::connect(&t, SIGNAL(testSignal()), &m, SLOT(testSlot()), Qt::UniqueConnection);
//指定同一个信号于同一个槽函数之间只有一个连接
t.start();
t.wait(5 * 1000);
t.quit();
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "main() tid = " << QThread::currentThreadId();
direct_connection();
// queued_connection(); //blocking_queued_connection(); //auto_connection(); //unique_connection(); return a.exec();}
运行结果如下:
立即调用:直接在发送信号的线程中调用槽函数,等价于槽函数的实时调用。直接忽略线程依附性,被调用的对象,直接在调用者的线程中被执行,注意目标线程和当前线程必须不同。
异步调用:信号发送至目标线程的事件队列,由目标线程进行处理,当前线程直接继续向下执行。槽函数在函数中被调用,信号发送后,子线程run()先结束,进入主线程事件队列。
同步调用:信号发送至目标线程的事件队列,由目标线程处理,当前线程等待槽函数返回,之后继续向下执行。不改变槽函数的线程依附性,只是等待子线程(槽函数)结束后,继续向下执行。注:目标(执行)线程和发送信号的线程必须是不同的。
默认连接:同一个信号可以多次连接到同一个槽函数,槽函数可以多次调用。
单一连接:功能与默认连接相同,自动确定连接类型,同一个信号与同一个槽函数之间只能有一个连接。
注:默认情况下,同一个信号可以多次连接到同一个槽函数,多次连接意味着同一个槽函数的多次调用。
上面代码中,信号与槽声明了两次,可是只执行了一次。
八、线程的生命期问题
C++对象有生命周期,线程也有生命周期,QThread对象的生命周期与对应的线程生命周期是否一致?
工程实践中的经验准则:线程对象的生命周期大于对应的线程的生命周期。
下面代码是否有问题:
void MyThread::run()
{
this->i = 1;
for(int i=0; i<5; i++)
{
this->i *= 2;
sleep(1);
}
}
void test()
{
MyThread t;
t.start();
}
局部对象脱离作用域以后自动销毁。所以在func()函数中,t.start()运行之后,run()还需要至少5秒钟才结束。
运行结果如下:
引入同步型线程设计概念:线程对象主动等待线程生命期结束后才销毁。
特点:同时直接在堆和栈中创建线程对象,对象销毁时确保线程生命期结束。
设计要点:在析构函数中调用wait()函数,强制等到线程运行结束。
适用场合:线程生命期较短的时候。
编程实现:
void SyncThread::run()
{
qDebug() << "void SyncThread::run() tid = " << currentThreadId();
for(int i=0; i<3; i++)
{
qDebug() << "void SyncThread::run() i = " << i;
sleep(1);
}
qDebug() << "void SyncThread::run() end";
}
SyncThread::~SyncThread()
{
wait(); //等待线程运行结束
qDebug() << "SyncThread::~SyncThread() destroy thread object";
}
在析构函数中等待对象线程运行结束,运行结果如下:
异步型线程设计:
概念:线程生命期结束时,通知销毁线程对象。
特点:只能在堆中创建线程对象,线程对象不能被外界主动销毁。
设计要点:在run()函数中调用deleteLater()函数,线程体函数主动申请销毁线程对象。
适用场合:线程生命期不可控,需要长时间运行于后台的情形。
编程实现:
AsyncThread* AsyncThread::NewInstance(QObject *parent)
{
return new AsyncThread(parent);
}
AsyncThread::AsyncThread(QObject *parent) :
QThread(parent)
{
}
void AsyncThread::run()
{
qDebug() << "void AsyncThread::run() tid = " << currentThreadId();
for(int i=0; i<3; i++)
{
qDebug() << "void AsyncThread::run() i = " << i;
sleep(1);
}
qDebug() << "void AsyncThread::run() end";
deleteLater();
}
AsyncThread::~AsyncThread()
{
qDebug() << "AsyncThread::~AsyncThread() destroy thread object";
}
void async_thread()
{
AsyncThread* at = AsyncThread::NewInstance();
at->start();
}
注意此处的二阶构造函数,以及对象是在堆上创建的。
运行结果如下:
总结:线程类的设计必须适应具体的场合,没有万能的设计,只有合适的设计。