注:此文是站在Qt5的角度说的,对于Qt4部分是不适用的。
1.先说Qt信号槽的几种连接方式和执行方式。
1)Qt信号槽给出了五种连接方式:
| Qt::AutoConnection | 0 | 自动连接:默认的方式。信号发出的线程和糟的对象在一个线程的时候相当于:DirectConnection, 如果是在不同线程,则相当于QueuedConnection |
| Qt::DirectConnection | 1 | 直接连接:相当于直接调用槽函数,但是当信号发出的线程和槽的对象不再一个线程的时候,则槽函数是在发出的信号中执行的。 |
| Qt::QueuedConnection | 2 | 队列连接:内部通过postEvent实现的。不是实时调用的,槽函数永远在槽函数对象所在的线程中执行。如果信号参数是引用类型,则会另外复制一份的。线程安全的。 |
| Qt::BlockingQueuedConnection | 3 | 阻塞连接:此连接方式只能用于信号发出的线程(一般是先好对象的线程) 和 槽函数的对象不再一个线程中才能用。通过信号量+postEvent实现的。不是实时调用的,槽函数永远在槽函数对象所在的线程中执行。但是发出信号后,当前线程会阻塞,等待槽函数执行完毕后才继续执行。 |
| Qt::UniqueConnection | 0x80 | 防止重复连接。如果当前信号和槽已经连接过了,就不再连接了。 |
2)信号槽的调用方式和线程:
UniqueConnection 模式:严格说不算连接方式,方式就是4中,此只是一个附加的参数。不讨论。
AutoConnection 模式:这个模式是默认的,但其可以看作是DirectConnection和QueuedConnection的自动选择,直接分析那两种也就行了。
发出信号,调用槽的方式也可以简单的分为两种:同步调用和异步调用
同步调用:发出信号后,当前线程等待槽函数执行完毕后才继续执行。
异步调用:发出信号后,立即执行剩下逻辑,不关心槽函数什么时候执行。
所以有下表:
| 线程/模式 | DirectConnection | QueuedConnection | BlockingQueuedConnection |
| 相同线程 | 直接调用,同步调用。 | 通过事件进行队列调用。异步调用. | 不可用 |
| 不同线程 | 直接调用。同步调用。槽函数在发出信号的线程执行。有线程安全隐患。 | 通过事件进行队列调用。异步调用.槽函数在对象所在的线程执行。线程安全。 | 通过事件进行阻塞调用。同步调用。槽函数在对象所在的线程执行。线程安全。 |
| Qt事件循环依赖 | 直接调用,不依赖Qt事件循环 | 通过事件进行队列调用。依赖,槽函数所在对象的线程必须启用Qt事件循环 | 通过事件进行队列调用,用信号量实现阻塞。依赖,槽函数所在对象的线程必须启用Qt事件循环 |
2.Qt信号连接多个槽,调用顺序。
先说基本原则:
槽函数开始调用的顺序和连接的顺序是一致的。
但是,上面也说了,有同步调用和异步调用。
对于同步调用,你观察的结果和基本原则一样。
但是对于异步调用,可能你最先连接的它,但是可能其他都执行完毕了,但是其还没执行。是因为对于异步调用:是开始调用的时候,生成一个需要调用这个函数的事件,然后放到事件队列里。然后立即返回,去执行调用其他槽函数或者槽函数都执行了,不关心槽函数的执行状态的。等到事件队列里任务轮到此事件再去调用。
3.信号的返回值。
大都说Qt信号槽不能使用返回值。其实不不准确的,Qt5中,信号槽是有返回值的。只是Qt的一个信号可以连接多个槽,还有同步调用和异步调用的问题,没发支持的很好,所以,返回值虽有,但只是鸡肋。
先说下返回值的规则把:
- 同步调用才有返回值,异步调用的返回值永远为返回值类型默认构造函数出来的。
- 连接的多个槽都返回值,那么结果是最后调用(连接)的那个。
也就是说对于QueuedConnection连接的信号槽,永远只是返回返回类型的默认构造函数的。对于AutoConnection连接的,如果发出信号的线程和槽函数线程不同亦然。
测试小例子地址:https://github.com/dushibaiyu/DsbyLiteExample/tree/master/QtSignalsSlotTest
4.信号参数的安全问题:
因为一个信号可以连接多个槽函数,如果参数是T * 或者是T &话会不会第一个槽函数改变参数的值,然后第二此调用的参数就已经不是信号发出的值?
1)对于T &: 在同步调用中则是变化的,不可用于异步,不可跨线程。所以BlockingQueuedConnection方式的同步也不行。(T& 不可用在队列调用(QueuedConnection)和阻塞调用(BlockingQueuedConnection)中。只能使用const T &。)
因为同步调用,你可以理解成直接调用,那么连接多个槽函数就相当于直接连续调用多个函数。类似于:
// 函数原型都是:void (int &a ) int a; fun1(a); fun2(a) ····· // 函数原型都是:void (int * a ) int a; pfun1(&a); pfun2(&a) ·····
这样,当第一个函数执行改变参数值之后,其后的函数调用都要受影响。
2) 对于T *,最好不要同时连接多个槽。
对于同步调用:是一个接着一个调用的,执行顺序类似上面,所以值也是每次调用也会变化的。
对于异步调用:其内容确实不确定的,因为异步调用的时间是不可控的。如果还有跨线程相关,则还有线程安全问题。