1+1=10

记记笔记,放松一下...

重看Qt中连接到同一signal的多个slots的执行顺序问题

十多年前基于Qt4简单写过 Qt中连接到同一signal的多个slots的执行顺序问题,或许是时候再回顾一下了...

首先,结论没有变:

1
in the order they have been connected

只是本次稍微扩充一下,同时将内容从Qt4更新到Qt6

  • 先看Qt手册【中的片段】
  • 用十个简短的例子进行演示说明
  • 再看Qt源码【中的片段】

Qt手册

学习Qt,还是建议手册优先。

在Qt6.5手册中

  • https://doc.qt.io/qt-6.5/signalsandslots.html:
1
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
1
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官方是这么说的:

1
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.
1
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"

运行结果如下(两个槽函数按连接顺序依次执行):

1
2
3
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可以看出是在次线程执行):

1
2
3
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"

如果直接运行的话,你看到的运行结果应该如下:

1
2
3
From main thread:  0x759c
"From  slot2: " 0x759c
"From  slot1: " 0x89e4

什么状况?说好的按顺序执行呢?!!怎么slot2抢跑了

其实理解偏差在"执行"上。对Queued方式,有两个层次的”执行“:

  1. emit信号时,将需要执行的槽函数信息,封装成一个event,丢到目标线程的事件队列中。这个时序是确定的
  2. 目标线程依次处理队列中的事件。何时处理并执行这个槽函数,对于emit线程来说,这个时间是不可控的(也不该控制)。

槽函数依次执行,只涵盖第一层次。

注意

上面结果是不严谨的。slot1和slot2位于两个不同线程中。具体谁先谁后是不确定的,也不能通过上面的实验测定(因为一般来说,Queued方式肯定会慢,毕竟目标线程需要发现它并执行)

只要这么改一下,就可以发现问题(如果你不能复现,请把100继续改大):

1
2
3
  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);

结果

1
2
3
4
5
6
7
8
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"

运行结果如下:

1
2
3
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"

运行结果如下:

1
2
3
4
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"

运行结果有时:

1
2
3
From main thread:  0x83c8
"From obj1 slot1: " 0x3540
"From obj2 slot1: " 0x636c

有时:

1
2
3
From main thread:  0x1818
"From obj2 slot1: " 0x9024
"From obj1 slot1: " 0x7984

同样,回归到我们例子三的解释中:

对Queued方式,有两个层次的”执行“:

  1. emit信号时,将需要执行的槽函数信息,封装成一个event,丢到目标线程的事件队列中。这个时序是确定的
  2. 目标线程依次处理队列中的事件。何时处理并执行这个槽函数,对于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"

运行结果如下:

1
2
3
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"

运行结果如下:

1
2
From main thread:  0x4f0
"From  slot1: " 0x4f0

从这儿可以发现,Qt::UniqueConnection 这个选项,是在connect时生效的。不管之前已有连接是什么类型,只要存在对应连接,当前connect就会被忽略。

注意

信号槽连接的新老用法混用,会不符合预期,比如,这个例子中,我们改成下面这样:

1
2
3
4
  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));

运行结果将为

1
2
3
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"

运行结果如下:

1
2
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();
}

运行结果如下:

1
2
3
"From obj2 slot1: " 0x3b18
"From obj2 slot1: " 0x3b18
"From obj2 slot1: " 0x3b18

自动连接,加上两次手动连接,共有三个connection。故而调用了三次。

在例子八中,我们提到信号槽新老写法不等价。那么connectSlotsByName()这个Qt4时代的产物,和那个连接等价呢?

用下面的写法即可验证你的猜想。

1
2
  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,可以看到下面的代码:

1
2
3
4
5
// 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方式,用到的信号量,是在这儿释放的:

1
2
3
4
5
6
7
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

Qt qt, c++