第一回 Signal和Slot是同步的還是異步的?
我們知道Qt以他的signal和slot機制獨步天下。但大家在用的時候有沒有注意過,signal和slot之間是異步的,還是同步的呢?為此我問過不少使用Qt的道友。有人說是同步的,有人說是異步的,也有人說要看當時你的人品。:( #$%^&*
為此貧道,特別做了以下幾個測試:
First,在main()主函數里,設置兩個基于QObject為父類的對象a和b,a觸發signal,b接受signal。請看具體案例:
1 class MyTestA : public QObject
2 {
3 Q_OBJECT
4 public:
5 void emitSignal()
6 {
7 signalMyTestA();
8 }
9
10 public slots:
11 void slotMyTestA()
12 {
13 qDebug()<<"slotMyTestA is called.";
14 }
15 signals:
16 void signalMyTestA();
17 };
18
19 class MyTestB : public QObject
20 {
21 Q_OBJECT
22 public slots:
23 void slotMyTestB()
24 {
25 qDebug()<<"slotMyTestB is called.";
26 }
27 signals:
28 void signalMyTestB();
29 };
30
31 int main(int argc, char *argv[])
32 {
33 QApplication app(argc, argv);
34
35 MyTestA a;
36 MyTestB b;
37 QObject::connect(&a,SIGNAL(signalMyTestA()),&b,SLOT(slotMyTestB()));
38
39 a.emitSignal();
40
41 return app.exec();
42 }
在slotMyTestB的函數里打個斷點,看一下調用堆棧(call stack)。
是同步調用的,某些道友開始拈胡微笑,實踐出真知啊。

此時只見東方黑云滾滾,電閃雷鳴,又有道友開始度劫了。突然一度劫道友橫眉冷對,拿起拂塵刷刷的改寫了上面的代碼。只見此道友把a對象挪到了一個新線程中(MyTestC創建的),而b對象仍然在主線程中。然后a對象觸發信號。
class MyTestA : public QObject
{
Q_OBJECT
public:
void emitSignal()
{
signalMyTestA();
}
public slots:
void slotMyTestA()
{
qDebug()<<"slotMyTestA is called.";
}
signals:
void signalMyTestA();
};
class MyTestB : public QObject
{
Q_OBJECT
public slots:
void slotMyTestB()
{
qDebug()<<"slotMyTestB is called.";
}
signals:
void signalMyTestB();
};
extern MyTestB *g_pMyTestB;
class MyTestC : public QThread
{
Q_OBJECT
public:
void run()
{
MyTestA a;
connect(&a,SIGNAL(signalMyTestA()),g_pMyTestB,SLOT(slotMyTestB()));
a.emitSignal();
exec();
}
public slots:
void slotMyTestC()
{
qDebug()<<"slotMyTestC is called.";
}
signals:
void signalMyTestC();
};
class MyTest : public QDialog
{
Q_OBJECT
public:
MyTest(QWidget *parent = 0, Qt::WFlags flags = 0);
~MyTest();
private:
Ui::MyTestClass ui;
};
////////////////////////////////////////////////
MyTestB *g_pMyTestB = NULL;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyTestB b;
g_pMyTestB = &b;
MyTestC c;
c.start();
return app.exec();
}
說時遲,那時快。在一道紫雷劈下之際,按下了F5。只見,此時的調用堆棧顯示,

奇跡出現了,居然變成異步調用了。只見東方天空萬道金光射下,在陣陣仙樂聲中,傳來朗朗之聲:"貧道塵事已了,再無牽掛"。
難道Qt真的是靠人品的,或者Qt莫不是也是修仙道友,不日也將飛升。
在吾等眾人膜拜加疑惑之時,只見飛升前輩,留下一條偈語。內事不決問百度,外事不決問谷歌。
吾等眾人立刻搜尋,恍然大物。
原來signal和slot是異步調用還是同步調用,取決于對connect的設定。其實connect還有一個參數(Qt::ConnectionType),是它決定了是同步還是異步。以下是ConnectionType的定義

只不過,平常它有一個默認值Qt::AutoConnection,我們忽略了它。這時有道友問道,為何在AutoConnection模式下,有時是同步,有時是異步,莫非Auto就是人品代名詞。
非也,其實Auto是這樣規定的,
當sender和receiver在同一線程時,就是同步模式,而在不同線程時,則是異步模式。
眾人皆曰善。
就在眾人彈冠相慶之時,突然一道類似眼鏡發出的寒光閃過,一個黑影漸漸清晰了起來。
他居然就是..................
青春永駐,十二年如一日的柯南君,他招牌式的磁性聲音給眾道友一晴天霹靂,“諸位以為這就是全部的真相嗎?”
接著他刷刷的又改寫了代碼,在主線程中生成a,b兩個對象,而a對象在新線程(MyTestC創建的)中觸發信號。
class MyTestA : public QObject
{
Q_OBJECT
public:
void emitSignal()
{
signalMyTestA();
}
public slots:
void slotMyTestA()
{
qDebug()<<"slotMyTestA is called.";
}
signals:
void signalMyTestA();
};
class MyTestB : public QObject
{
Q_OBJECT
public slots:
void slotMyTestB()
{
qDebug()<<"slotMyTestB is called.";
}
signals:
void signalMyTestB();
};
extern MyTestB *g_pMyTestB;
extern MyTestA *g_pMyTestA;
class MyTestC : public QThread
{
Q_OBJECT
public:
void run()
{
g_pMyTestA->emitSignal();
exec();
}
public slots:
void slotMyTestC()
{
qDebug()<<"slotMyTestC is called.";
}
signals:
void signalMyTestC();
};
/////////////////////////////////////////////
MyTestB *g_pMyTestB = NULL;
MyTestA *g_pMyTestA = NULL;
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
MyTestA a;
g_pMyTestA = &a;
MyTestB b;
g_pMyTestB = &b;
QObject::connect(&a,SIGNAL(signalMyTestA()),&b,SLOT(slotMyTestB()));
MyTestC c;
c.start();
return app.exec();
}
在眾人疑惑的眼光中,此君淡定的按下了F5。只見調用堆棧(call stack)顯示

眾人皆驚呼,“Impossible”。a和b明明是屬于一個線程的,為何會異步調用。此時我們熟悉的語錄,又在耳邊回響,是"我相信真相只有一個!!!"這句話嗎?No,只見柯南君,優雅地揮了揮手指,"Nothing impossible",從口中緩緩滑出。
。眾人皆撲街,“有屁快放”。
此時柯南君緩緩從口袋中,摸出一張紙,拋向空中,然后轉身離去。只見隨風飄落的紙面上面摘錄了這么一段Qt源代碼,在Auto模式下,如果要同步調用,不僅要求sender和receiver是同一線程,而且sender觸發的時候,所在的線程也要和receiver一致。
// determine if this connection should be sent immediately or
// put into the event queue
if ((c->connectionType == Qt::AutoConnection
&& (currentThreadData != sender->d_func()->threadData
|| receiver->d_func()->threadData != sender->d_func()->threadData))
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
continue;
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
blocking_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv);
continue;
}
摘自qobject.cpp
z眾人皆驚,原來在Auto模式下,如果sender的觸發時所處的線程和receiver不同,也會是異步調用。此時道友齊聲向柯南喊道“這是全部的真相了嗎”?柯南轉過頭來笑而不語,漸漸又消失在黑暗中。“有多少無恥可以重來”漂上了眾人的心頭。望著遠處的雨后陽光,一個大大的問號也出現在眾人頭頂,"Qt你到底有多無恥???"。眾人又陷入了沉思。
欲知后事如何,請聽下回分解。
此篇已在CNBLOG同時發布
posted on 2011-08-26 10:41
櫻桃小錘子 閱讀(12944)
評論(10) 編輯 收藏 引用