信號(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:後者具體原因待驗證