Qt 中的多线程 02:移动工作对象到子线程中

发布时间 2023-12-27 16:36:33作者: Jeffxue

Qt 中的多线程除了通过继承 QThread 类,然后重写虚函数 run() 之外还有另一种方案:将要执行的复杂操作,放在一个继承自 QObject 的类中,然后使用 QObject::moveToThread(QThread*) 函数将工作对象的移动到创建的子线程中去执行。

工作对象类

该类继承自 QObject 主要是用来实现一些耗时复杂的操作,这些操作可能会阻塞主线程,因此需要在后面将其移到子线程中去执行。

1、MyWork.h 文件

#pragma once

#include <QObject>

class MyWork: public QObject
{
    Q_OBJECT
public:
    MyWork();
    ~MyWork();

signals:

    /**
     * 子线程用来向主线程发送信号.
     */
    void  SendMsg(QString msg);

public slots:

    /**
     * 复杂操作在槽函数中实现,然后通过信号槽来与主线程建立联系
     * 以触发该复杂的槽函数执行。
     */
    void DoSomething();

};

2、MyWork.cpp 文件

#include "MyWork.h"
#include <QThread>

MyWork::MyWork()
{

}

MyWork::~MyWork()
{

}

void MyWork::DoSomething()
{
    SendMsg(QString("子线程id = %1").arg((int)QThread::currentThreadId()));
    SendMsg("Begin Work ......");

    QThread::sleep(6);

    SendMsg("Work Finished.");
}

主线程类

将在主线程类中去创建子线程,和 工作对象,并将工作对象移动到子线程中,然后通过主程序的信号来触发位于子线程中的工作对象,让其执行一些复杂的操作。

1、QThreadTest.h 文件

#pragma once

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

#include "MyWork.h"

class QThreadTest : public QMainWindow
{
    Q_OBJECT

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

signals:

    /**
     *  主线程通过触发信号,来让子线程开始执行工作.
     */
    void BeginWork();

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

    /**
    * @brief  通过点击Button来触发对应的信号,然位于子线程中的工作对象开始执行对应的函数
    */
    void OnBtn02Clicked();

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

private:
    Ui::QThreadTestClass ui;
    QThread *subThread;     // 创建子线程,用来将任务移动到该线程中去执行
};

2、QThreadTest.cpp 文件

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

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

    subThread = new QThread(this);

    // 创建工作对象,不能为其指定父指针,否则后面无法将其移动子线程中去执行
    MyWork* myWork = new MyWork();

    // 将工作对象移动到子线程中去执行
    myWork->moveToThread(subThread);

    // 在子线程执行结束之后,将工作对象的资源进行释放
    connect(subThread, &QThread::finished, myWork, &MyWork::deleteLater);

    // 通过主线程的信号来触发子线程开始执行对应的工作
    connect(this, &QThreadTest::BeginWork, myWork, &MyWork::DoSomething);

    // 在子线程中的工作对象无法直接修改主线程的UI,因此需要通过信号槽的方式来进行更新
    connect(myWork, &MyWork::SendMsg, this, &QThreadTest::GetWorkMsg);

    // 启动子线程,此时只是子线程开始执行,但是子线程中的具体工作 DoSomething 还未执行
    subThread->start();


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

QThreadTest::~QThreadTest()
{
    // 创建的线程一定要进行释放,否则关闭程序的时候线程仍然在运行,此时会造成程序Crash
    subThread->quit();
    subThread->wait();
}

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

    emit BeginWork(); // 触发信号,子线程中开始执行工作对象的函数
}

void QThreadTest::GetWorkMsg(QString msg)
{
    ui.textBrowser_02->append(msg);
}


【总结】

该方法的主要流程为:

  1. 创建一个类,如 MyWork 继承自 QObject
  2. MyWork 类中将复杂的操作放在槽函数中,后续会通过信号槽的方式来触发子线程执行该操作。
  3. MyWork 类中定义信号槽,用于子线程向主线程更新数据或信息。
  4. 在主线程中创建 QThread 对象,和 MyWork类对象,并将 工作对象移动到子线程中。
  5. 分别建立信号槽:
    • 子线程执行完工作后,释放工作对象。(此时子线程可能并未退出)
    • 主线程的开始执行信号连接工作对象的槽函数。
    • 子线程向主线程发送数据,用来更新UI或同步操作。
  6. 启动子线程,此时工作对象的复杂操作还未执行。
  7. 在主线程中某一时机下,触发开始在子线程中执行工作类中的槽函数,此时才会进行复杂操作的执行。