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设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是为了避免重复连接。
 

五、信号槽的原理

  1. moc查找头文件中的signals,slots,标记出信号和槽。
  2. 将信号槽信息存储到类静态变量staticMetaObject中,并且按声明顺序进行存放,建立索引。
  3. 当发现有connect连接时,将信号槽的索引信息放到一个map中,彼此配对。
  4. 当调用emit时,调用信号函数,并且传递发送信号的对象指针,元对象指针,信号索引,参数列表到active函数
  5. 通过active函数找到在map中找到所有与信号对应的槽索引
  6. 根据槽索引找到槽函数,执行槽函数。
     

参考:

QT信号槽实现原理 - 简书

nno信号与槽的连接方式 - MaxBruce - 博客园c

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐