前言

Qt的信号槽的连接方式有五种,每种代表的含义,从文档可以知道

Qt::AutoConnection:(默认)如果接收者是发出信号的线程,则使用Qt::DirectConnection。否则,将使用Qt::QueuedConnection。连接类型在信号发出时被确定。

Qt::DirectConnection:当信号被发出时,槽被立即调用。该槽是在信号线程中执行的。

Qt::QueuedConnection:当控制权返回到接收方线程的事件循环时,槽被调用。该槽在接收方的线程中执行。

Qt::BlockingQueuedConnection:与Qt::QueuedConnection相同,只是信号线程会阻塞,直到槽返回。如果接收者在信号线程中,就不能使用这种连接,否则应用程序会陷入死锁。

Qt::UniqueConnection:这是一个可以与上述任何一种连接类型相结合的标志,使用的方法是和之前的标志或运算。当Qt::UniqueConnection被设置时,如果连接已经存在,QObject::connect()将失败(即如果相同的信号已经连接到同一对对象的同一槽中)。

测试

现在编写代码测试一下

  1#include <QCoreApplication>
  2#include <QDebug>
  3#include <QThread>
  4#include <QDateTime>
  5#include <thread>
  6#include <chrono>
  7#include <iostream>
  8#include <QTimer>
  9
 10void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
 11{
 12    QString log("[%1] [%2] %3");
 13    QString dateTime = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss.zzz");
 14    QChar ch;
 15    switch (type) {
 16        case QtDebugMsg:
 17            ch = 'D';
 18            break;
 19        case QtInfoMsg:
 20            ch = 'I';
 21            break;
 22        case QtWarningMsg:
 23            ch = 'W';
 24            break;
 25        case QtCriticalMsg:
 26            ch = 'C';
 27            break;
 28        case QtFatalMsg:
 29            ch = 'F';
 30            break;
 31    }
 32    log = log.arg(dateTime, ch, msg);
 33    std::cout << log.toStdString() << std::endl;
 34}
 35
 36struct Tool
 37{
 38    QString toolName;
 39};
 40
 41struct TaskData
 42{
 43    int taskId;
 44    QString worker;
 45    Tool *tool = nullptr;
 46};
 47
 48QMap<QThread *, QString> threadMap;
 49
 50QStringList names{
 51        "dhimmied",
 52        "chumhaeds",
 53        "hulmol",
 54        "birran",
 55        "vamets",
 56        "ogmath",
 57};
 58
 59class ThreadA : public QThread
 60{
 61Q_OBJECT
 62public:
 63    explicit ThreadA(QObject *parent = nullptr) : QThread(parent){}
 64protected:
 65    void run() override
 66    {
 67        threadMap[QThread::currentThread()] = metaObject()->className();
 68        for (int i = 0; i < names.size(); ++i) {
 69            qDebug() << "begin dispatchTask" << "thread: " << threadMap[QThread::currentThread()];
 70            Tool tool{"t" + QString::number(i)};
 71            TaskData td{i, names[i], &tool};
 72            emit dispatchTask(td);
 73            qDebug() << "finish dispatchTask" << "thread: " << threadMap[QThread::currentThread()];
 74            msleep(1000);
 75        }
 76    }
 77signals:
 78    void dispatchTask(const TaskData &data);
 79};
 80
 81
 82class ThreadB : public QObject
 83{
 84Q_OBJECT
 85public:
 86    explicit ThreadB(QObject *parent = nullptr) : QObject(parent)
 87    {
 88        auto thread = new QThread;
 89        moveToThread(thread);
 90        thread->start();
 91    }
 92
 93    void doTask(const TaskData &data)
 94    {
 95        qDebug() << "doTask: {" << data.taskId << ", " << data.worker
 96//                 << "tool: " << data.tool->toolName
 97                 << "}" << "thread: " << threadMap[QThread::currentThread()];
 98        std::this_thread::sleep_for(std::chrono::seconds(3));
 99    }
100};
101
102int main(int argc, char *argv[])
103{
104    QCoreApplication app(argc, argv);
105    qInstallMessageHandler(myMessageOutput);
106    ThreadA a;
107    ThreadB b;
108    qRegisterMetaType<TaskData>("TaskData");
109    QObject::connect(&a, &ThreadA::dispatchTask, &b, &ThreadB::doTask,
110                     Qt::ConnectionType::AutoConnection);
111    a.start();
112    threadMap[b.thread()] = b.metaObject()->className();
113    threadMap[QThread::currentThread()] = "mainThread";
114    return QCoreApplication::exec();
115}
116
117#include "test_signal_slot.moc"

说明

发送任务线程A间隔1s发送信号给接收任务线程B,线程B每个任务执行需要3s,存在滞后性。线程B采用moveToThread方式创建可以理解为在线程B中运行了一个额外的事件时间循环可以实时响应信号执行槽函数,也可以这么写:

 1class ThreadB : public QThread
 2{
 3Q_OBJECT
 4public:
 5    explicit ThreadB(QObject *parent = nullptr) : QThread(parent) {}
 6protected:
 7    void run() override
 8    {
 9        threadMap[QThread::currentThread()] = ThreadB::metaObject()->className();
10        QEventLoop ev;
11        while (true) {
12            ev.processEvents();
13            msleep(100);
14        }
15    }
16public:
17    void doTask(const TaskData &data)
18    {
19        qDebug() << "doTask: {" << data.taskId << ", " << data.worker
20                 //                 << "tool: " << data.tool->toolName
21                 << "}" << "thread: " << threadMap[QThread::currentThread()];
22        std::this_thread::sleep_for(std::chrono::seconds(3));
23    }
24};

结果分析

Qt::AutoConnection

Qt::AutoConnection下,任务发送和执行按照各自的时序运行,1s发送一次,3s执行一次,输出结果:

[2021/12/31 15:48:39.281] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:39.281] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:39.281] [D] doTask: { 0 ,  "dhimmied" } thread:  "ThreadB"
[2021/12/31 15:48:40.281] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:40.281] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:41.281] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:41.282] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:42.281] [D] doTask: { 1 ,  "chumhaeds" } thread:  "ThreadB"
[2021/12/31 15:48:42.282] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:42.282] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:43.282] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:43.282] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:44.282] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:44.282] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:48:45.282] [D] doTask: { 2 ,  "hulmol" } thread:  "ThreadB"
[2021/12/31 15:48:48.282] [D] doTask: { 3 ,  "birran" } thread:  "ThreadB"
[2021/12/31 15:48:51.282] [D] doTask: { 4 ,  "vamets" } thread:  "ThreadB"
[2021/12/31 15:48:54.282] [D] doTask: { 5 ,  "ogmath" } thread:  "ThreadB"

Qt::DirectConnection

而在Qt::DirectConnection下,所有的信号关联的槽函数都在发送信号的线程执行

[2021/12/31 15:49:20.064] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:20.064] [D] doTask: { 0 ,  "dhimmied" } thread:  "ThreadA"
[2021/12/31 15:49:23.065] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:24.065] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:24.065] [D] doTask: { 1 ,  "chumhaeds" } thread:  "ThreadA"
[2021/12/31 15:49:27.065] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:28.065] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:28.065] [D] doTask: { 2 ,  "hulmol" } thread:  "ThreadA"
[2021/12/31 15:49:31.066] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:32.066] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:32.066] [D] doTask: { 3 ,  "birran" } thread:  "ThreadA"
[2021/12/31 15:49:35.066] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:36.067] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:36.067] [D] doTask: { 4 ,  "vamets" } thread:  "ThreadA"
[2021/12/31 15:49:39.067] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:40.067] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:49:40.067] [D] doTask: { 5 ,  "ogmath" } thread:  "ThreadA"
[2021/12/31 15:49:43.067] [D] finish dispatchTask thread:  "ThreadA"

Qt::QueuedConnection

Qt::AutoConnection 在多线程连接时和 Qt::QueuedConnection 一样

Qt::BlockingQueuedConnection

Qt::BlockingQueuedConnection 会在另外一个线程槽函数执行完之前阻塞,等到执行完才往下执行

[2021/12/31 15:53:30.054] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:30.054] [D] doTask: { 0 ,  "dhimmied" } thread:  "ThreadB"
[2021/12/31 15:53:33.054] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:34.055] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:34.055] [D] doTask: { 1 ,  "chumhaeds" } thread:  "ThreadB"
[2021/12/31 15:53:37.055] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:38.055] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:38.056] [D] doTask: { 2 ,  "hulmol" } thread:  "ThreadB"
[2021/12/31 15:53:41.056] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:42.056] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:42.056] [D] doTask: { 3 ,  "birran" } thread:  "ThreadB"
[2021/12/31 15:53:45.057] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:46.057] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:46.057] [D] doTask: { 4 ,  "vamets" } thread:  "ThreadB"
[2021/12/31 15:53:49.057] [D] finish dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:50.057] [D] begin dispatchTask thread:  "ThreadA"
[2021/12/31 15:53:50.058] [D] doTask: { 5 ,  "ogmath" } thread:  "ThreadB"
[2021/12/31 15:53:53.058] [D] finish dispatchTask thread:  "ThreadA"

另外,通过队列的方式,信号槽中有数据的话会缓存到事件队列,但是如果指针指向发出信号函数的局部变量,可能该变量被析构导致调用出错,此时需要使用共享指针更安全;事件队列如果步调一直不一致也会称为程序的安全隐患,比如队列会溢出

其他

在线程中需要通过主线程执行函数,比如控件的update(),可以使用QTimer::singleShot

1QTimer::singleShot(0, [widget] { widget->update(); })