前言

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

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

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

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

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

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

测试

现在编写代码测试一下

#include <QCoreApplication>
#include <QDebug>
#include <QThread>
#include <QDateTime>
#include <thread>
#include <chrono>
#include <iostream>
#include <QTimer>

void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
    QString log("[%1] [%2] %3");
    QString dateTime = QDateTime::currentDateTime().toString("yyyy/MM/dd hh:mm:ss.zzz");
    QChar ch;
    switch (type) {
        case QtDebugMsg:
            ch = 'D';
            break;
        case QtInfoMsg:
            ch = 'I';
            break;
        case QtWarningMsg:
            ch = 'W';
            break;
        case QtCriticalMsg:
            ch = 'C';
            break;
        case QtFatalMsg:
            ch = 'F';
            break;
    }
    log = log.arg(dateTime, ch, msg);
    std::cout << log.toStdString() << std::endl;
}

struct Tool
{
    QString toolName;
};

struct TaskData
{
    int taskId;
    QString worker;
    Tool *tool = nullptr;
};

QMap<QThread *, QString> threadMap;

QStringList names{
        "dhimmied",
        "chumhaeds",
        "hulmol",
        "birran",
        "vamets",
        "ogmath",
};

class ThreadA : public QThread
{
Q_OBJECT
public:
    explicit ThreadA(QObject *parent = nullptr) : QThread(parent){}
protected:
    void run() override
    {
        threadMap[QThread::currentThread()] = metaObject()->className();
        for (int i = 0; i < names.size(); ++i) {
            qDebug() << "begin dispatchTask" << "thread: " << threadMap[QThread::currentThread()];
            Tool tool{"t" + QString::number(i)};
            TaskData td{i, names[i], &tool};
            emit dispatchTask(td);
            qDebug() << "finish dispatchTask" << "thread: " << threadMap[QThread::currentThread()];
            msleep(1000);
        }
    }
signals:
    void dispatchTask(const TaskData &data);
};


class ThreadB : public QObject
{
Q_OBJECT
public:
    explicit ThreadB(QObject *parent = nullptr) : QObject(parent)
    {
        auto thread = new QThread;
        moveToThread(thread);
        thread->start();
    }

    void doTask(const TaskData &data)
    {
        qDebug() << "doTask: {" << data.taskId << ", " << data.worker
//                 << "tool: " << data.tool->toolName
                 << "}" << "thread: " << threadMap[QThread::currentThread()];
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }
};

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);
    qInstallMessageHandler(myMessageOutput);
    ThreadA a;
    ThreadB b;
    qRegisterMetaType<TaskData>("TaskData");
    QObject::connect(&a, &ThreadA::dispatchTask, &b, &ThreadB::doTask,
                     Qt::ConnectionType::AutoConnection);
    a.start();
    threadMap[b.thread()] = b.metaObject()->className();
    threadMap[QThread::currentThread()] = "mainThread";
    return QCoreApplication::exec();
}

#include "test_signal_slot.moc"

说明

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

class ThreadB : public QThread
{
Q_OBJECT
public:
    explicit ThreadB(QObject *parent = nullptr) : QThread(parent) {}
protected:
    void run() override
    {
        threadMap[QThread::currentThread()] = ThreadB::metaObject()->className();
        QEventLoop ev;
        while (true) {
            ev.processEvents();
            msleep(100);
        }
    }
public:
    void doTask(const TaskData &data)
    {
        qDebug() << "doTask: {" << data.taskId << ", " << data.worker
                 //                 << "tool: " << data.tool->toolName
                 << "}" << "thread: " << threadMap[QThread::currentThread()];
        std::this_thread::sleep_for(std::chrono::seconds(3));
    }
};

结果分析

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

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