十多年前基于Qt4简单写过 Qt中连接到同一signal的多个slots的执行顺序问题,或许是时候再回顾一下了...
首先,结论没有变:
| in the order they have been connected
|
只是本次稍微扩充一下,同时将内容从Qt4更新到Qt6
- 先看Qt手册【中的片段】
- 用十个简短的例子进行演示说明
- 再看Qt源码【中的片段】
Qt手册
学习Qt,还是建议手册优先。
在Qt6.5手册中
- https://doc.qt.io/qt-6.5/signalsandslots.html:
| If several slots are connected to one signal, the slots will be executed one after the other,
|
- https://doc.qt.io/qt-6.5/qobject.html
| If a signal is connected to several slots, the slots are activated in the same order in which the connections were made, when the signal is emitted.
|
需要注意的是,一些老的书籍或资料中,认为槽函数的执行顺序是随机的。
这是因为在Qt4.5以及之前版本中,Qt官方是这么说的:
| If several slots are connected to one signal, the slots will be executed one after the other, in an arbitrary order, when the signal is emitted.
|
| If a signal is connected to several slots, the slots are activated in an arbitrary order when the signal is emitted.
|
个人理解,这个顺序一直是确定的,只是早期官方不想让用户依赖这个顺序。
手册中的这一变化,是从Qt4.6开始的。另外,Qt::UniqueConnection 这一个连接类型,也是在Qt4.6引入的。
另外,Qt6.0 引入新的连接类型 Qt::SingleShotConnection。
全家福:
枚举量 |
值 |
描述 |
Qt::AutoConnection |
0 |
这个是默认值。 如果接受者位于emit所在线程,则使用Qt::DirectConnection,否则使用Qt::QueuedConnection |
Qt::DirectConnection |
1 |
基础类型,等同于直接调用 |
Qt::QueuedConnection |
2 |
基础类型,发送一个事件到接收者所在线程的事件队列,接收者线程处理到该事件时,调用对应的槽函数。 |
Qt::BlockingQueuedConnection |
3 |
与Qt::QueuedConnection类似,emit所在线程会被阻塞 |
Qt::UniqueConnection |
0x80 |
Qt4.6 引入,可与上面类型进行”与“操作。 |
Qt::SingleShotConnection |
0x100 |
Qt6.0 引入,可与上面类型进行”与“操作。 |
执行顺序演示
我们还是以具体的例子开始。
为了简单起见,每个例子均只有一个main.cpp文件构成,可用cmake或qmake构建。
例子一
简单场景,使用 Qt::AutoConnection 模式,单线程模式:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44 | // By dbzhang800, 2023-12-02
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
class MyObject : public QObject
{
Q_OBJECT
public:
MyObject() {}
signals:
void dbSignal();
public slots:
void dbSlot1() {
qDebug() << QString("From %1 slot1: ").arg(objectName())
<< QThread::currentThreadId();
}
void dbSlot2() {
qDebug() << QString("From %1 slot1: ").arg(objectName())
<< QThread::currentThreadId();
}
void dbSlot3() {
qDebug() << QString("From %1 slot3: ").arg(objectName())
<< QThread::currentThreadId();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot2);
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果如下(两个槽函数按连接顺序依次执行):
| From main thread: 0x8468
"From slot1: " 0x8468
"From slot2: " 0x8468
|
例子二
为了节省篇幅,后面的例子只包含main函数,其他部分和例子一一样(其实每个例子只有几行代码的差异)。
简单场景,使用 Qt::AutoConnection 模式,多线程模式(2个线程):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot2);
QThread thread;
obj.moveToThread(&thread);
thread.start();
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果如下(两个槽函数按连接顺序依次执行,从线程ID可以看出是在次线程执行):
| From main thread: 0x9058
"From slot1: " 0x8f00
"From slot2: " 0x8f00
|
槽函数在同一个线程中执行,emit时会放置两个事件(event)进目标线程的事件队列。目标线程依次取出并执行它们,很自然。
注意 :
本例中,刻意将线程操作放置到connect之后。用以展示:
AutoConnection 到底等同于 DirectConnection还是QueuedConnection,是在emit时确定的,而不是connect时!!
例子三
直接使用 Qt::DirectConnection 和 Qt::QueuedConnection,多线程模式(2个线程):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, Qt::QueuedConnection);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot2, Qt::DirectConnection);
QThread thread;
obj.moveToThread(&thread);
thread.start();
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
如果直接运行的话,你看到的运行结果应该如下:
| From main thread: 0x759c
"From slot2: " 0x759c
"From slot1: " 0x89e4
|
什么状况?说好的按顺序执行呢?!!怎么slot2抢跑了
其实理解偏差在"执行"上。对Queued方式,有两个层次的”执行“:
- emit信号时,将需要执行的槽函数信息,封装成一个event,丢到目标线程的事件队列中。这个时序是确定的
- 目标线程依次处理队列中的事件。何时处理并执行这个槽函数,对于emit线程来说,这个时间是不可控的(也不该控制)。
槽函数依次执行,只涵盖第一层次。
注意
上面结果是不严谨的。slot1和slot2位于两个不同线程中。具体谁先谁后是不确定的,也不能通过上面的实验测定(因为一般来说,Queued方式肯定会慢,毕竟目标线程需要发现它并执行)
只要这么改一下,就可以发现问题(如果你不能复现,请把100继续改大):
| QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, Qt::QueuedConnection);
for (int i = 0; i< 100; ++i)
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot2, Qt::DirectConnection);
|
结果
| From main thread: 0x77b4
"From slot2: " 0x77b4
"From slot2: " 0x77b4
...
"From slot2: " 0x77b4
"From slot1: " 0x982c
"From slot2: " 0x77b4
...
|
例子四
如果就是接受不了三的结果,非要同步怎么办?
改变一行代码,引入 Qt::BlockingQueuedConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, Qt::BlockingQueuedConnection);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot2, Qt::DirectConnection);
QThread thread;
obj.moveToThread(&thread);
thread.start();
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果如下:
| From main thread: 0x6d44
"From slot1: " 0x9b2c
"From slot2: " 0x6d44
|
现在舒服多了,唯一问题是,这会阻塞当前线程。
例子五
继续看看 Qt::BlockingQueuedConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot2);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot3, Qt::BlockingQueuedConnection);
QThread thread;
obj.moveToThread(&thread);
thread.start();
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果如下:
| From main thread: 0x3634
"From slot1: " 0x43fc
"From slot2: " 0x43fc
"From slot3: " 0x43fc
|
在emit发生时,它会依次把 slot1、slot2、slot3对应的事件放置到目标线程的事件队列中,然后等待slot3的事件被去除并执行完毕。
这造成所有的槽函数都会阻塞当前线程,尽管执行顺序符合预期。
例子六
考虑真实的多线程场景,如下,我们使用两个工作线程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj1;
obj1.setObjectName("obj1");
MyObject obj2;
obj2.setObjectName("obj2");
QObject::connect(&obj1, &MyObject::dbSignal, &obj1, &MyObject::dbSlot1);
QObject::connect(&obj1, &MyObject::dbSignal, &obj2, &MyObject::dbSlot1);
QThread thread1;
obj1.moveToThread(&thread1);
thread1.start();
QThread thread2;
obj2.moveToThread(&thread2);
thread2.start();
//thread.wait(1000);
emit obj1.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果有时:
| From main thread: 0x83c8
"From obj1 slot1: " 0x3540
"From obj2 slot1: " 0x636c
|
有时:
| From main thread: 0x1818
"From obj2 slot1: " 0x9024
"From obj1 slot1: " 0x7984
|
同样,回归到我们例子三的解释中:
对Queued方式,有两个层次的”执行“:
- emit信号时,将需要执行的槽函数信息,封装成一个event,丢到目标线程的事件队列中。这个时序是确定的
- 目标线程依次处理队列中的事件。何时处理并执行这个槽函数,对于emit线程来说,这个时间是不可控的(也不该控制)。
槽函数依次执行,只涵盖第一层次。而第二层次的不确定,这刚好是多线程的特质。
其他选项
例子七
在例子三中,我们顺便演示了,同一个信号和槽之间可以多次建立连接,效果是槽函数执行多次。
那么,如何避免这种问题??只想执行一次怎么办??
这就需要 Qt::UniqueConnection 出场了...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, Qt::QueuedConnection);
for (int i = 0; i< 100; ++i) {
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot2,
static_cast<Qt::ConnectionType>(Qt::DirectConnection|Qt::UniqueConnection));
}
QThread thread;
obj.moveToThread(&thread);
thread.start();
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果如下:
| From main thread: 0x1844
"From slot2: " 0x1844
"From slot1: " 0x8820
|
从结果看,重复连接被干掉了。
例子八
新的例子,继续看 Qt::UniqueConnection
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, Qt::DirectConnection);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, static_cast<Qt::ConnectionType>(Qt::QueuedConnection|Qt::UniqueConnection));
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, static_cast<Qt::ConnectionType>(Qt::DirectConnection|Qt::UniqueConnection));
QThread thread;
obj.moveToThread(&thread);
thread.start();
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果如下:
| From main thread: 0x4f0
"From slot1: " 0x4f0
|
从这儿可以发现,Qt::UniqueConnection 这个选项,是在connect时生效的。不管之前已有连接是什么类型,只要存在对应连接,当前connect就会被忽略。
注意
信号槽连接的新老用法混用,会不符合预期,比如,这个例子中,我们改成下面这样:
| QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, Qt::DirectConnection);
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, static_cast<Qt::ConnectionType>(Qt::QueuedConnection|Qt::UniqueConnection));
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, static_cast<Qt::ConnectionType>(Qt::DirectConnection|Qt::UniqueConnection));
QObject::connect(&obj, SIGNAL(dbSignal()), &obj, SLOT(dbSlot1()), static_cast<Qt::ConnectionType>(Qt::DirectConnection|Qt::UniqueConnection));
|
运行结果将为
| From main thread: 0x82f4
"From slot1: " 0x82f4
"From slot1: " 0x82f4
|
也就是最后一行没起到UniqueConnection作用。
例子九
看看Qt6引入的,Qt::SingleShotConnection的选项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "From main thread: " << QThread::currentThreadId();
MyObject obj;
QObject::connect(&obj, &MyObject::dbSignal, &obj, &MyObject::dbSlot1, Qt::SingleShotConnection);
emit obj.dbSignal();
emit obj.dbSignal();
emit obj.dbSignal();
return a.exec();
}
#include "main.moc"
|
运行结果如下:
| From main thread: 0x4f0
"From slot1: " 0x4f0
|
不管信号触发多少次,槽函数第一次被调用后,这个链接就断开(disconnect)了。
注意对比:Qt::UniqueConnection效力发生在connect方式时,Qt::SingleShotConnection效力发生在emit执行时。
例子十
最后,尽管不追求十全十美,还是凑个数凑到十吧。看一下使用Qt designer时,非常常用(或者被动无意识在用)的信号槽自动连接,即QMetaObject::connectSlotsByName()
介入进来会怎么样。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35 | // By dbzhang800, 2023-12-02
#include <QCoreApplication>
#include <QDebug>
#include <QThread>
class MyObject2 : public QObject
{
Q_OBJECT
public:
MyObject2() {setObjectName("obj2");}
signals:
void dbSignal();
public slots:
void on_obj2_dbSignal() {
qDebug() << QString("From %1 slot1: ").arg(objectName())
<< QThread::currentThreadId();
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyObject2 obj2;
QMetaObject::connectSlotsByName(&obj2);
QObject::connect(&obj2, &MyObject2::dbSignal, &obj2, &MyObject2::on_obj2_dbSignal);
QObject::connect(&obj2, SIGNAL(dbSignal()), &obj2, SLOT(on_obj2_dbSignal()));
emit obj2.dbSignal();
return a.exec();
}
|
运行结果如下:
| "From obj2 slot1: " 0x3b18
"From obj2 slot1: " 0x3b18
"From obj2 slot1: " 0x3b18
|
自动连接,加上两次手动连接,共有三个connection。故而调用了三次。
在例子八中,我们提到信号槽新老写法不等价。那么connectSlotsByName()这个Qt4时代的产物,和那个连接等价呢?
用下面的写法即可验证你的猜想。
| QObject::connect(&obj2, &MyObject2::dbSignal, &obj2, &MyObject2::on_obj2_dbSignal);
QObject::connect(&obj2, SIGNAL(dbSignal()), &obj2, SLOT(on_obj2_dbSignal()), Qt::UniqueConnection);
|
源码
知其然,也要知其所以然。要不?简单看看Qt源码?
保存connection信息
QObject的connect函数会调用到下面这个函数来保存当前连接信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 | QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
int signal_index, const QMetaObject *smeta,
const QObject *receiver, int method_index,
const QMetaObject *rmeta, int type, int *types)
{
...
QObjectPrivate::ConnectionData *scd = QObjectPrivate::get(s)->connections.loadRelaxed();
if (type & Qt::UniqueConnection && scd) {
if (scd->signalVectorCount() > signal_index) {
const QObjectPrivate::Connection *c2 = scd->signalVector.loadRelaxed()->at(signal_index).first.loadRelaxed();
int method_index_absolute = method_index + method_offset;
while (c2) {
if (!c2->isSlotObject && c2->receiver.loadRelaxed() == receiver && c2->method() == method_index_absolute)
return nullptr;
c2 = c2->nextConnectionList.loadRelaxed();
}
}
}
type &= ~Qt::UniqueConnection;
const bool isSingleShot = type & Qt::SingleShotConnection;
type &= ~Qt::SingleShotConnection;
...
std::unique_ptr<QObjectPrivate::Connection> c{new QObjectPrivate::Connection};
c->sender = s;
c->signal_index = signal_index;
c->receiver.storeRelaxed(
r);
QThreadData *td = r->d_func()->threadData.loadAcquire();
td->ref();
c->receiverThreadData.storeRelaxed(
td);
c->method_relative = method_index;
c->method_offset = method_offset;
c->connectionType = type;
c->isSlotObject = false;
c->argumentTypes.storeRelaxed(
types);
c->callFunction = callFunction;
c->isSingleShot = isSingleShot;
QObjectPrivate::get(s)->addConnection(signal_index, c.get());
...
}
|
这个函数做了大量的判断,并过滤掉UniqueConnection,生成一个Connection,将其放置到内部的链表中。
信号触发
首先,我们知道Qt中所谓signal,就是一个普通的函数,只是不用我们自己去实现它。moc会自动生成一个 moc_xxxx.cpp或者xxxx.moc文件,里面包含这个信号的实现。这样,C++编译器才能正常编译Qt程序,这部分可以参考从C++到Qt。
对我们前面的例子中,打开 main.moc,可以看到下面的代码:
| // SIGNAL 0
void MyObject::dbSignal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}
|
信号触发(调用信号函数时)时,activate进而会调用如下的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 | template <bool callbacks_enabled>
void doActivate(QObject *sender, int signal_index, void **argv)
{
....
QObjectPrivate::ConnectionDataPointer connections(sp->connections.loadRelaxed());
QObjectPrivate::SignalVector *signalVector = connections->signalVector.loadRelaxed();
const QObjectPrivate::ConnectionList *list;
if (signal_index < signalVector->count())
list = &signalVector->at(signal_index);
else
list = &signalVector->at(-1);
....
uint highestConnectionId = connections->currentConnectionId.loadRelaxed();
do {
QObjectPrivate::Connection *c = list->first.loadRelaxed();
if (!c)
continue;
do {
....
// determine if this connection should be sent immediately or
// put into the event queue
if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
|| (c->connectionType == Qt::QueuedConnection)) {
queued_activate(sender, signal_index, c, argv);
continue;
#if QT_CONFIG(thread)
} else if (c->connectionType == Qt::BlockingQueuedConnection) {
if (receiverInSameThread) {
qWarning(
"Qt: Dead lock detected while activating a BlockingQueuedConnection: "
"Sender is %s(%p), receiver is %s(%p)",
sender->metaObject()->className(), sender,
receiver->metaObject()->className(), receiver);
}
if (c->isSingleShot && !QObjectPrivate::removeConnection(c))
continue;
QSemaphore semaphore;
{
QMutexLocker locker(signalSlotLock(receiver));
if (!c->isSingleShot && !c->receiver.loadAcquire())
continue;
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal_index, argv, &semaphore) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction,
sender, signal_index, argv, &semaphore);
QCoreApplication::postEvent(receiver, ev);
}
semaphore.acquire();
continue;
#endif
}
if (c->isSingleShot && !QObjectPrivate::removeConnection(c))
continue;
QObjectPrivate::Sender senderData(receiverInSameThread ? receiver : nullptr, sender, signal_index);
if (c->isSlotObject) {
SlotObjectGuard obj{c->slotObj};
{
obj->call(receiver, argv);
}
} else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
//we compare the vtable to make sure we are not in the destructor of the object.
const int method_relative = c->method_relative;
const auto callFunction = c->callFunction;
const int methodIndex = (Q_HAS_TRACEPOINTS || callbacks_enabled) ? c->method() : 0;
callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv);
} else {
const int method = c->method_relative + c->method_offset;
QMetaObject::metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv);
}
....
} while ((c = c->nextConnectionList.loadRelaxed()) != nullptr && c->id <= highestConnectionId);
} while (list != &signalVector->at(-1) && ((list = &signalVector->at(-1)), true));
|
这个函数很长:
- 找到信号对应的connection列表
- 遍历列表
- 对于Queued连接,调用
queued_activate()
处理
- 对于BlockingQueued连接,发送QMetaCallEvent事件,使用信号量 QSemaphore并等待其释放。
- 其他,分情况调用
对于queued_activate
来说,主要就是生成并发送QMetaCallEvent事件到接收者线程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv)
{
const int *argumentTypes = c->argumentTypes.loadRelaxed();
....
if (argumentTypes == &DIRECT_CONNECTION_ONLY) // cannot activate
return;
....
QMetaCallEvent *ev = c->isSlotObject ?
new QMetaCallEvent(c->slotObj, sender, signal, nargs) :
new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal, nargs);
....
if (c->isSingleShot && !QObjectPrivate::removeConnection(c)) {
delete ev;
return;
}
....
QCoreApplication::postEvent(receiver, ev);
}
|
当然,这个事件,最终会到事件处理函数中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 | bool QObject::event(QEvent *e)
{
...
case QEvent::MetaCall:
{
QAbstractMetaCallEvent *mce = static_cast<QAbstractMetaCallEvent*>(e);
if (!d_func()->connections.loadRelaxed()) {
QMutexLocker locker(signalSlotLock(this));
d_func()->ensureConnectionData();
}
QObjectPrivate::Sender sender(this, const_cast<QObject*>(mce->sender()), mce->signalId());
mce->placeMetaCall(this);
break;
}
...
|
再展开一下
1
2
3
4
5
6
7
8
9
10
11
12 | void QMetaCallEvent::placeMetaCall(QObject *object)
{
if (d.slotObj_) {
d.slotObj_->call(object,
d.args_);
} else if (d.callFunction_ && d.method_offset_ <= object->metaObject()->methodOffset()) {
d.callFunction_(object, QMetaObject::InvokeMetaMethod, d.method_relative_, d.args_);
} else {
QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod,
d.method_offset_ + d.method_relative_, d.args_);
}
}
|
而前面提到的BlockingQueued方式,用到的信号量,是在这儿释放的:
| QAbstractMetaCallEvent::~QAbstractMetaCallEvent()
{
#if QT_CONFIG(thread)
if (semaphore_)
semaphore_->release();
#endif
}
|
参考
- https://woboq.com/blog/how-qt-signals-slots-work.html
- https://woboq.com/blog/how-qt-signals-slots-work-part3-queuedconnection.html