QT通过connect关联信号和槽函数
一、槽函数的执行是同步还是异步
在同一个线程中,Qt信号槽的执行是同步的。当一个信号被发射时,槽函数会立即被调用,而不是被放入事件队列中。这是因为在同一个线程中,事件循环和槽函数都是在同一个线程中执行的,所以槽函数的执行不会阻塞信号的发射者或其他槽函数的执行。
在不同的线程中,Qt信号槽的执行是异步的。当一个信号被发射时,槽函数不会立即被调用,而是被放入接收对象所在线程的事件队列中,等待事件循环处理。由于事件循环和槽函数在不同的线程中执行,因此槽函数的执行可能会被其他线程的操作所阻塞,这也就是为什么Qt建议在槽函数中避免执行耗时的操作的原因。
需要注意的是,如果在槽函数中执行的操作比较耗时,会导致当前线程被阻塞,从而影响应用程序的响应性能。因此,在编写槽函数时,应该尽量避免执行耗时的操作,或者将这些操作放在单独的线程中执行。
二、connect的Qt5以前的写法
Qt5以前的connect有以下几种:
bool connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
bool connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
bool connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const
Qt4的SIGNAL和SLOT两个宏,实际是将其参数转换成相应的字符串。在编译之前,Qt的moc工具从源代码中提取出所需要的元数据,形成一张由使用了signals和slots修饰的所有函数组成的字符串表。connect函数将与信号关联起来的槽的字符串,同这张字符串表中的信息进行比较匹配,也就能够在发出信号时知道需要调用哪个槽函数。
注意,不能将全局函数或者 Lambda 表达式传入connect()。使用字符串导致了Qt4有以下缺点:一旦出现连接不成功的情况,Qt 4 是没有编译错误的(因为一切都是字符串,编译期是不检查字符串是否匹配),而是在运行时给出错误。这无疑会增加程序的不稳定性。
connect(obj1, SIGNAL(fun1(param1, param2,...)), obj2, SLOT(fun2(param1,...)));
// 举个例子
QLabel *label = new QLabel;
QScrollBar *scrollBar = new QScrollBar;
QObject::connect(scrollBar, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)));
优点:
对所有控件都适用。
缺点:
书写繁琐,槽函数必须在slot标签下。在程序编译阶段,程序会将函数以字符串的形式进行链接,程序不会检查信号/槽函数是否存在,只有在运行期间才会验证是否正确
三、Qt5的写法
为了解决Qt4中connect的问题,Qt5写法有以下几种
QMetaObject::Connection connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const;
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
const QObject *, PointerToMemberFunction,
Qt::ConnectionType)
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
Functor);
举个例子
QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
QObject::connect(lineEdit, &QLineEdit::textChanged,
label, &QLabel::setText);
优点:
书写简便,编译期间就会检查信号与槽是否存在,参数类型检查,Q_OBJECT是否存在,槽函数不在限定必须是slot,可以是普通的函数、类的普通成员函数、lambda函数。
缺点:
函数重载,有可能会造成程序的困扰,不知道该具体链接哪个
四、connect的Qt::ConnectionType(信号与槽的关联类型)
Qt文档如下
翻译过来是这样:
第5个参数一般不填,为默认值。
1、Qt::AutoConnection: 默认值,使用这个值则连接类型会在信号发送时决定。如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
2、Qt::DirectConnection:槽函数会在信号发送的时候直接被调用,槽函数和信号发送者在同一线程。效果看上去就像是直接在信号发送位置调用了槽函数,效果上看起来像函数调用,同步执行。
emit语句后面的代码将在与信号关联的所有槽函数执行完毕后才被执行。
3、Qt::QueuedConnection:信号发出后,信号会暂时被放到一个消息队列中,需等到接收对象所属线程的事件循环取得控制权时才取得该信号,然后执行和信号关联的槽函数,这种方式既可以在同一线程内传递消息也可以跨线程操作。
emit语句后的代码将在发出信号后立即被执行,无需等待槽函数执行完毕
4、Qt::BlockingQueuedConnection:槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。而且接收者和发送者绝对不能在一个线程,否则程序会死锁。在多线程间需要同步的场合可能需要这个。
5、Qt::UniqueConnection:这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接。
五、信号槽的原理
- moc查找头文件中的signals,slots,标记出信号和槽。
- 将信号槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引。
- 当发现有connect连接时,将信号槽的索引信息放到一个map中,彼此配对。
- 当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数
- 通过active函数找到在map中找到所有与信号对应的槽索引
-
根据槽索引找到槽函数,执行槽函数。
参考: