信号(signal)与槽(slot)是Qt特有的机制,它可以让控件间的通信变的很方便。你也可以很轻易地使用一个signal绑定多个slot,本文谈一下一个signal绑定多个slot时,slot的执行顺序。
connect函数
signal和slot通过connect连接,继承于QObject的类可以使用这种机制。
[static] QMetaObject::Connection QObject::connect(
const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type = Qt::AutoConnection)
它将sender的signal与receiver的method绑定,当你调用emit signal时,它便会调用触发receiver的method。不过它还有第五个参数,Qt::ConnectionType连接类型,你可能没有了解过它。
信号与槽的连接类型
Qt::ConnectionType连接类型由一个enum(枚举)描述。
enum Qt::ConnectionType
它确定你所连接的signal在触发时,是立即调用slot函数,还是进入队列等待后调用。它的具体描述如下:
constant | value | Description |
---|---|---|
Qt::AutoConnection | 0 | (默认)如果receiver位于发送signal的线程中,则使用Qt::DirectConnection,否则使用Qt::QueuedConnection。具体类型在信号发出时判定。 |
Qt::DirectConnection | 1 | 当signal发出时,slot立即被调用。slot在signal线程中执行 |
Qt::QueuedConnection | 2 | 当控制返回到receiver的线程中时,slot被调用。slot执行在receiver的线程中。 |
Qt::BlockingQueuedConnection | 3 | 与Qt::QueuedConnection相同,只是发出signal的线程堵塞,知道插槽返回。如果receiver位于发送signal线程,则不能使用此连接,否则应用程序死锁。 |
Qt::UniqueConnection | 0x80 | (按位或)组合模式 (Qt4.6后引入) |
队列类型的连接,参数必须是Qt元对象系统所知的类型,因为Qt需要拷贝参数以将它们存储在后台事件中。如果你使用未知类型,将会报以下错误。
QObject::connect: Cannot queue arguments of type 'MyType'
测试代码
由于使用场景需要,我这里只讨论单线程中,使用单signal触发多个slot能不能产生多线程的效果。
Qt::AutoConnection
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = nullptr);
~MainWindow();
signals:
void sig();
private slots:
void on_pushButton_clicked();
void slotA();
void slotB();
private:
Ui::MainWindow *ui;
int m_nT;
};
#endif // MAINWINDOW_H
// mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QDebug>
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
m_nT = 0;
connect(this, SIGNAL(sig()), SLOT(slotA()), Qt::AutoConnection);
connect(this, SIGNAL(sig()), SLOT(slotB()), Qt::AutoConnection);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_pushButton_clicked()
{
emit sig();
//QTimer::singleShot(0, this, SLOT(slotA1()));
//QTimer::singleShot(0, this, SLOT(slotA2()));
printf("cccc");
}
void MainWindow::slotA()
{
for (int i = 0; i < 10; i++)
printf("A%d", m_nT++);
}
void MainWindow::slotB()
{
for (int i = 0; i < 10; i++)
printf("B%d", m_nT++);
}
运行后,点击按钮发送sigA(),多次测试,记录观察结果。
发现结果始终如下,
A0A1A2A3A4A5A6A7A8A9B10B11B12B13B14B15B16B17B18B19cccc
我们可以得到两个slot函数不存在交错运行。结合文档的描述,这边receiver在sender线程中,则使用Qt::DirectConnection,当signal发出时,slot立即被调用。slot在signal线程中执行。
所以该情况下,slotA和slotB都运行在一个线程(主线程)中,它们是顺序执行的(单线程)。sig发送,立即触发slotA,执行完slotA,然后触发slotB,再执行printf(“cccc”)。
Qt::QueuedConnection
若把上面代码中slotA和slotB的连接类型都改为Qt::QueuedConnection。
Qt::QueuedConnection,当控制返回到receiver的线程中时,slot被调用。slot执行在receiver的线程中。
多次测试,结果如下:
ccccA0A1A2A3A4A5A6A7A8A9B10B11B12B13B14B15B16B17B18B19
同样可以看到,两个slot函数是不存在交错运行的。但是这次printf(“cccc”)打印在slotA和slotB之前了。可以推测,发送sig的时候,主线程的当前函数还在执行,此时slotA和slotB一起进入队列等待主线程当前函数执行完毕,再依次执行。这与DirectConnection连接的立即执行有所不同。
1.QueuedConnection 2.AutoConnection
多次测试,结果如下:
B0B1B2B3B4B5B6B7B8B9ccccA10A11A12A13A14A15A16A17A18A19
可以得到QueuedConnection的优先级是在主线程之后的,等到主线程当前函数执行完,才会执行对应的slot函数。
1.AutoConnection 2.QueuedConnection
多次测试,结果如下:
A0A1A2A3A4A5A6A7A8A9ccccB10B11B12B13B14B15B16B17B18B19
结论
单线程中使用单signal绑定多slot,触发slot函数执行也是单线程执行,其执行有先后顺序。
单线程中DirectConnection对应的slot函数会被立刻调用,优先级高于当前执行函数,QueuedConnection对应的slot函数优先级低于当前执行函数(回到接收对象的事件循环中时再进行调用)。
Note:后者具体原因待验证