Qt 中的多线程 :重写 run 函数

发布时间 2023-12-24 22:59:12作者: Jeffxue

Qt 中的多线程主要是为了防止复杂耗时的操作阻塞主线程,导致界面卡死的问题。可以通过继承 QThread 类后,重写 run() 函数来实现。

一、 定义继承自 QThread 的类

定义一个类继承自 QThread,并重写虚函数 run(),将耗时的操作放在 run()函数中,然后在主线程中来通过调用该类的 start() 函数,从而实现启动子线程执行 run()函数中的内容。

1、 定义继承自 QThreadWorkThread ,作为子线程

WorkThread.h 内容如下:

#pragma once
#include <QThread>

/// <summary>
/// 通过继承 QThread类,并重写 run 函数来实现多线程操作,
/// 将复杂的操作放在run函数里面,run()函数相当于运行在一个新的线程中。
/// 需要公有继承 QThread 类,否则后面无法调用重载的 run 函数
/// </summary>
class WorkThread: public QThread
{
    Q_OBJECT

public:
    WorkThread();
    ~WorkThread();

    /**
    * @brief  在该函数中实现耗时阻塞主线程的操作,该函数中的操作将会在子线程中执行
    */
    void run() override;

signals:

    /**
     * 子线程无法直接操作UI,因此只能通过信号槽的机制来向主线程发送信号,
     * 主线程接受到该信号后,进行更新UI内容,从而实现间接操作UI.
     */
    void SendMsg(QString msg);
};


WorkThread.cpp 内容如下:

#include "WorkThread.h"

WorkThread::WorkThread()
{

}

WorkThread::~WorkThread()
{
    // 使用quit()或者exit()使得子线程能够退出消息循环,而不会一直阻塞在子线程中
    this->quit();

    // 等待子线程退出后,回收子线程的资源
    this->wait();
}

void WorkThread::run()
{
    int curId = (int)QThread::currentThreadId();

    emit SendMsg(QString("子线程id = %1").arg(curId));

    emit SendMsg("Start do something ...");

    QThread::sleep(10);

    QString resStr = "Finish complex operation";

    emit SendMsg(resStr);

    // 子线程中的消息循环函数,如果没有该函数,则子线程执行完后会立即退出
    // this->exec();
}

2、在主线程中调用子线程

QThreadTest.h 内容如下:

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_QThreadTest.h"

#include "WorkThread.h"

class QThreadTest : public QMainWindow
{
    Q_OBJECT

public:
    QThreadTest(QWidget *parent = Q_NULLPTR);
    ~QThreadTest();

private slots:
    /**
     * 直接在主线程中进行耗时操作,会直接阻塞主UI线程,导致程序卡死.
     */
    void OnBtn01Clicked();

    /**
    * @brief  将创建一个线程来执行复杂的操作,避免阻塞UI线程。
    *         该类继承自 QThread,将复杂的操作写在 run()中,
    *         run()函数中的内容才会在子线程中执行。
    */
    void OnBtn02Clicked();

    /**
    * @brief  由于子线程不能直接操作UI,所以该函数用于将接受到的子线程内容显示在UI上
    * @param  [in]  msg
    */
    void GetResult(QString msg);

private:
    Ui::QThreadTestClass ui;

    WorkThread* workThread;  // 子线程的对象指针
};

QThreadTest.cpp 内容如下:

#include "QThreadTest.h"
#include <QPushButton>

QThreadTest::QThreadTest(QWidget *parent)
    : QMainWindow(parent)
{
    ui.setupUi(this);

    // 在构造函数中初始化子线程的对象指针
    workThread = new WorkThread();

    // 子线程只能通过信号槽来间接的修改UI的内容
    connect(workThread, &WorkThread::SendMsg, this, &QThreadTest::GetResult);

    // 子线程结束之后释放其所占用的资源
    connect(workThread, &WorkThread::finished, workThread, &WorkThread::deleteLater);


    connect(ui.btn01, &QPushButton::clicked, this, &QThreadTest::OnBtn01Clicked);
    connect(ui.btn02, &QPushButton::clicked, this, &QThreadTest::OnBtn02Clicked);
}

QThreadTest::~QThreadTest()
{
}

void QThreadTest::OnBtn01Clicked()
{
    ui.textBrowser_01->append("Start do something ...");

    QThread::sleep(5);

    ui.textBrowser_01->append("Finishing this operation.");
}

void QThreadTest::OnBtn02Clicked()
{
    ui.textBrowser_02->append(QString("主线程id = %1").arg((int)QThread::currentThreadId()));

    // 启动子线程,开始执行 run 函数中的耗时操作,避免阻塞主线程。
    workThread->start();
}

void QThreadTest::GetResult(QString msg)
{
    ui.textBrowser_02->append("workThread: " + msg);
}

效果如下图所示:

点击 Btn01 后直接导致程序卡住,无法响应其它的操作。点击Btn02后会在子线程中执行耗时操作,此时主 UI 线程仍然可以响应用户的操作,不会阻塞主线程。


【注意】