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

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);

private:
    int num1;
};


WorkThread.cpp 内容如下:

#include "WorkThread.h"

WorkThread::WorkThread()
{

}

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

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

void WorkThread::run()
{
    num1++ ;

    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 线程仍然可以响应用户的操作,不会阻塞主线程。


【注意】

  1. 如果在主线程的析构函数中,即~QThreadTest()中来释放子线程的资源,必须要确保子线程 workThread 的资源还未退出释放,即 run()函数中有 exec()消息循环来阻止子线程释放资源。否则子线程执行完后会自动退出并释放资源,此时在主线程的析构函数中再次释放子线程的资源则会导致重复释放的异常。
  2. QThread 的派生类 WorkThread 中,除了 run() 函数是在新的子线程中执行的,其它的函数都是在实例化 WorkThread 类对象的线程中执行的(即主线程中),如WorkThread类的构造函数,析构函数都是在主线程中执行的。且 WorkThread 类的对象也属于主线程。
  3. WorkThread 类的成员变量 num1 既可以被 run() 函数所在的子线程访问,也可以被主线程所访问。因此在访问修改该变量的时候需要确保是否安全,来避免数据的不一致性,必要的时候需要加锁进行保护。
posted @ 2023-12-24 22:54  Jeffxue  阅读(357)  评论(0编辑  收藏  举报