Qt中connect的第五个参数与多线程

237 阅读3分钟

本文仅关注connect第五个参数中Qt::QueuedConnectionQt::DirectConnection在多线程中的使用方法与差别。

所有5个参数的定义如下:

enum ConnectionType {
    AutoConnection,
    DirectConnection,
    QueuedConnection,
    BlockingQueuedConnection,
    UniqueConnection =  0x80
};

Qt::QueuedConnectionQt::DirectConnection的简介:

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

Qt::DirectConnection:信号发出时将立即调用该槽函数。槽函数在信号发送者所在的线程中执行

这里重点关注下我加粗的部分。

多线程事件循环中使用Qt::QueuedConnection的执行流程如下:

thread: 2中出发的信号被投递到了thread: 1的事件循环中;当thread: 1中的SignnalEvent被处理时就会执行对应的槽函数,槽函数在接收者的线程中执行

Image description

多线程事件循环中使用Qt::DirectConnection的执行流程如下:

thread: 1中出发的信号直接调用了槽函数,不论槽函数是属于哪个对象的,槽函数在信号发送者所在的线程中执行

Image description

实现方式1(继承QThread)

下面的代码会有3部分组成:worker_thread.hprocesser.hmain.cpp

WorkerThread类继承了QThread类并重写了run()方法,在run()方法中创建了定时器并启动当前线程的事件循环,QTimer::timeout信号以Qt::DirectConnection的方式关联了WorkerThread::onTimeout()槽函数,由于QTimer对象在run()方法中被创建并关联的槽函数并使用了Qt::DirectConnection参数,所以当QTimer::timeout触发时WorkerThread::onTimeout()的所在线程与run()所在线程是同一个。在WorkerThread::onTimeout()只做一件事,就是触发WorkerThread::timeout信号,该WorkerThread::timeout信号的所在线程与run()的也相同。

#ifndef WORKER_THREAD_H
#define WORKER_THREAD_H

#include <QThread>
#include <QTimer>
#include <QDebug>
#include <QObject>

/**
 * @brief The WorkerThread class
 *
 */
class WorkerThread: public QThread {
    Q_OBJECT
public:
    WorkerThread(QObject *parent=nullptr): QThread(parent) {
    }
protected:
    void run() override {
        qDebug() << "WorkerThread thread id: " << QThread::currentThreadId();
        m_timer = new QTimer();
        connect(m_timer, &QTimer::timeout, this, &WorkerThread::onTimeout, Qt::DirectConnection);
        m_timer->start(1000);
        exec();
    }
signals:
    void timeout();

private slots:
    void onTimeout() {
        emit timeout();
    }

private:
    QTimer *m_timer;
};

#endif // WORKER_THREAD_H

Processer类就干一件事情,就是提供了一个公有的void onTimeout()槽函数,在这个槽函数中打印出线程id,用于确定当前的槽函数在哪个线程中执行。

#ifndef PROCESSER_H
#define PROCESSER_H
#include <QObject>
#include <QThread>
#include <QDebug>

class Processer: public QObject {
    Q_OBJECT
public:
    Processer(QObject *parent=nullptr): QObject(parent) {
        qDebug() << "Processer thread id: " << QThread::currentThreadId();
    }
public slots:
    void onTimeout() {
        qDebug() << "Processer slot thread id: " << QThread::currentThreadId();
    }
};

#endif // PROCESSER_H

在main函数中将WorkerThread::timeout的信号与Processer::onTimeout槽进行关联

采用Qt::QueuedConnection关联
#include <QCoreApplication>
#include "worker_thread.h"
#include "Processer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "Main thread id: " << QThread::currentThreadId();

    WorkerThread *workerThread = new WorkerThread;
    Processer *processer = new Processer;
    QObject::connect(workerThread, &WorkerThread::timeout, processer, &Processer::onTimeout, Qt::QueuedConnection);
    workerThread->start();
    return a.exec();
}

Image description

可以看到Processser::onTimeout的线程id是与主线程(执行main函数的线程)相同而与WorkerThread::run()的线程id不同,证明事件被投递到主线程的事件循环中被处理了。

采用Qt::DirectConnection关联
#include <QCoreApplication>
#include "worker_thread.h"
#include "Processer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    qDebug() << "Main thread id: " << QThread::currentThreadId();

    WorkerThread *workerThread = new WorkerThread;
    Processer *processer = new Processer;
    QObject::connect(workerThread, &WorkerThread::timeout, processer, &Processer::onTimeout, Qt::DirectConnection);
    workerThread->start();
    return a.exec();
}

Image description

可以看到Processser::onTimeout的线程id与WorkerThread::run()中输出的线程id相同而与主线程id不同,证明槽函数直接被WorkerThread::run()的线程调用了。

关注点

  • QTimer对象的创建时机
  • QTimer对象的timeout事件与WorkerThread的关联方式为什么用Qt::DirectConnection而不是Qt::QueuedConnection
  • 在线程函数run()中运行exec()后会发生什么

实现方式2(QObject::moveToThread)

Processer类的代码和实现方式1的相同,下面主要看下main函数:

Qt::QueuedConnection方式:

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include "processer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "Main thread id: " << QThread::currentThreadId();

    QTimer *timer = new QTimer();
    timer->start(1000);

    Processer *processer = new Processer();
    QObject::connect(timer, &QTimer::timeout, processer, &Processer::onTimeout, Qt::QueuedConnection);

    QThread *thread = new QThread();
    timer->moveToThread(thread);
    thread->start();

    return a.exec();
}

Image description

可以看到槽函数的线程id和主函数是相同的。

Qt::DirectConnection方式:

#include <QCoreApplication>
#include <QThread>
#include <QTimer>
#include "processer.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    qDebug() << "Main thread id: " << QThread::currentThreadId();

    QTimer *timer = new QTimer();
    timer->start(1000);

    Processer *processer = new Processer();
    QObject::connect(timer, &QTimer::timeout, processer, &Processer::onTimeout, Qt::DirectConnection);

    QThread *thread = new QThread();
    timer->moveToThread(thread);
    thread->start();

    return a.exec();
}

Image description

可以看到槽函数的线程id是一个新的线程id,与主线程id是不同的。

总结

目前官方更推荐使用QObject::moveToThread的方式使用多线程。