QT的信号槽机制和线程的启动方式已经在前面的文章中写过了,本文主要是对信号槽的连接方式进行解读,信号槽的连接方式一共有5种:
1.Qt::DirectConnection
发出信号后立即调用槽函数。 该槽函数在信号函数所在的线程中执行。
示例
//头文件
#ifndef SIGNALSLOTTEST_H
#define SIGNALSLOTTEST_H
#include <QObject>
#include <QThread>
#include <iostream>
#include <cassert>
class signalslottest : public QObject
{
Q_OBJECT
public:
explicit signalslottest(QObject *parent = nullptr);
virtual ~signalslottest();
signals:
void emitsignal();
private:
QThread workthread_;
};
class work:public QObject
{
Q_OBJECT
public:
explicit work(QObject *parent = nullptr);
virtual ~work();
void dowork();
};
#endif // SIGNALSLOTTEST_H
//源文件
#include "signalslottest.h"
using namespace std;
signalslottest::signalslottest(QObject *parent) : QObject(parent)
{
cout<<QThread::currentThreadId()<<endl;
work *pw=new work();
pw->moveToThread(&workthread_);
connect(this, &signalslottest::emitsignal, pw, &work::dowork, Qt::DirectConnection);
workthread_.start();
emit emitsignal();
cout<<__func__<<endl;
}
signalslottest::~signalslottest()
{
cout<<__func__<<endl;
}
work::work(QObject *parent) : QObject(parent)
{
cout<<__func__<<endl;
}
work::~work()
{
cout<<__func__<<endl;
}
void work::dowork()
{
cout<<__func__<<endl;
QThread::sleep(2);
cout<<QThread::currentThreadId()<<endl;
}
//主函数
#include <QCoreApplication>
#include "signalslottest.h"
using namespace std;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
signalslottest t;
cout<<__func__<<endl;
cout<<QThread::currentThreadId()<<endl;
return a.exec();
}
上述代码采用https://blog.csdn.net/Master_Cui/article/details/109209175中的第一种方式启动线程,输出结果如下
上述代码首先创建一个signalslottest的对象,在signalslottest的构造函数中,启动线程并将work对象放在分线程中执行,然后发射信号并调用void work::dowork()
由于信号槽的连接方式是DirectConnection,所以,信号发射后,马上执行槽函数void work::dowork(),当槽函数执行完,退出后,接着执行信号函数后面的代码
此外,即使槽函数被移动到其他线程,可是由于信号槽的连接方式是DirectConnection,所以,槽函数最终也会在信号函数所在的线程(主线程)中执行
2.Qt::QueuedConnection
当程序切换到接收者所在线程的事件循环时,将调用槽函数。 该槽函数在接收者的线程中执行。
示例
依然采用Qt::DirectConnection的示例代码,并将信号槽的连接方式改为Qt::QueuedConnection
connect(this, &signalslottest::emitsignal, pw, &work::dowork, Qt::QueuedConnection);
执行结果如下
因为信号槽分别处在两个线程,而且信号槽的连接方式是QueuedConnection。所以,当信号发出后,并不会马上执行槽函数,而是将信号所在线程的代码执行完后,再去执行槽函数所在线程的代码
3.Qt::AutoConnection
如果在调用connect时,如果不指定连接方式,则Qt::AutoConnection是默认的连接方式。该连接方式表示:如果槽函数和信号函数位于同一线程,那么,连接方式是Qt :: DirectConnection。 否则,连接方式是Qt :: QueuedConnection。
示例
依然采用Qt::DirectConnection的示例代码,并将信号槽的连接方式改为Qt::AutoConnection
connect(this, &signalslottest::emitsignal, pw, &work::dowork, Qt::AutoConnection);
执行结果如下
因为信号槽分别处在两个线程,而且信号槽的连接方式是AutoConnection。所以,连接方式实际上是QueuedConnection,所以,时序和QueuedConnection一致,如果将void work::dowork()放到主线程中,那么连接方式实际上是DirectConnection
4.Qt::BlockingQueuedConnection
与Qt :: QueuedConnection相同,只不过信号线程将一直阻塞直到槽函数执行结束。 如果接收者位于信号线程中,则一定不要使用此连接方式,否则将造成死锁。
示例
依然采用Qt::DirectConnection的示例代码,并将信号槽的连接方式改为Qt::BlockingQueuedConnection
connect(this, &signalslottest::emitsignal, pw, &work::dowork, Qt::BlockingQueuedConnection);
执行结果如下
时序看起来和DirectConnection的时序相同。原因是因为信号发出后,信号所在的线程(主线程)会阻塞,当槽函数执行结束后,又回到主线程继续执行
使用BlockingQueuedConnection时,信号和槽函数一定不能在同一线程
示例
依然采用Qt::DirectConnection的示例代码,在类signalslottest中添加一个槽函数slotfunc(),将信号槽的连接方式改为Qt::BlockingQueuedConnection
class signalslottest : public QObject
{
Q_OBJECT
public:
explicit signalslottest(QObject *parent = nullptr);
virtual ~signalslottest();
public slots:
void slotfunc() {std::cout<<__func__<<std::endl;}
signals:
void emitsignal();
private:
QThread workthread_;
};
signalslottest::signalslottest(QObject *parent) : QObject(parent)
{
cout<<QThread::currentThreadId()<<endl;
work *pw=new work();
pw->moveToThread(&workthread_);
connect(this, &signalslottest::emitsignal, pw, &work::dowork, Qt::BlockingQueuedConnection);
connect(this, &signalslottest::emitsignal, this, &signalslottest::slotfunc, Qt::BlockingQueuedConnection);
workthread_.start();
emit emitsignal();
cout<<__func__<<endl;
}
执行结果如下
因为信号和槽函数都在同一个线程执行,并且连接方式是BlockingQueuedConnection,所以,当执行完dowork后,程序出现死锁,而且,上述代码也说明,当一个信号连接多个槽函数时,槽函数的执行顺序和connect的顺序一致
5.Qt::UniqueConnection
该标志可以使用按位或操作将其与上述任何一种连接类型组合在一起。 设置Qt::UniqueConnection时,如果连接已经存在,则QObject :: connect()将失败。(即,如果同一信号已经连接到同一对象的同一槽函数两次,那么,第二次connect时,连接会失败)
其实UniqueConnection并不算是一种连接方式,只是配合其他连接方式一起使用,防止重复连接而已
示例
signalslottest::signalslottest(QObject *parent) : QObject(parent)
{
cout<<QThread::currentThreadId()<<endl;
work *pw=new work();
pw->moveToThread(&workthread_);
connect(this, &signalslottest::emitsignal, pw, &work::dowork, static_cast<Qt::ConnectionType>(Qt::QueuedConnection | Qt::UniqueConnection));
connect(this, &signalslottest::emitsignal, pw, &work::dowork, Qt::QueuedConnection);
workthread_.start();
emit emitsignal();
cout<<__func__<<endl;
}
通过上述代码的执行结果可见,即使使用了UniqueConnection,并且同一信号槽函数连接两次,信号槽函数的连接依然能正常建立,槽函数依旧执行了两次,所以,UniqueConnection并不像官网说的能防止信号槽重复连接
此外,当使用UniqueConnection时,要么单独使用,如果想和其他方式做按位与运算,那么与运算的结果是一个int数据,不是枚举ConnectionType,会提示如下错误
所以要用static_cast做类型转换,将int转换成一个枚举
参考
https://doc.qt.io/qt-5/qt.html#ConnectionType-enum
欢迎大家评论交流,作者水平有限,如有错误,欢迎指出