【QT】基于QObject::moveToThread()创建后台线程,如何优雅地退出。

测试环境:Win11 & Qt_6_6_1 & Mingw-64

完成时间:2024/3/10 

问题描述

当子线程运行在一个阻塞调用上时,主线程退出并尝试回收子线程资源失败。

后台线程创建的一般流程

  1. 在主线程创建业务线程对象backendTaskThread_,以及被其接管的业务对象backendTask_;
  2. 线程对象的启动信号 绑定 业务对象业务循环(槽函数);
  3. 启动业务线程;

后台线程退出的一般流程

  1. 退出业务循环;
  2. 退出线程;
  3. 与主线程会合;

关键逻辑

  • 后台线程正常运行期间,线程调度权一直被业务循环所独占,而通过moveToThread转移线程所有权后,其信号槽的执行被业务循环所阻塞;
  • 如果业务循环是一个阻塞在自己定义的逻辑中,比如一个while(true)结构,就可以向其中嵌入一个自定义的退出标志,或者直接使用QThread所提供的中断请求(主线程发起requestInterruption(),业务循环检查isInterruptionRequested()返回值),自主选择退出业务循环;
  • 如果是阻塞在第三方接口上,无法向其内部嵌入退出标志检查逻辑,但一般都会提供对应的退出接口,比如stop()、quit()等;
  • 由于无法使用信号槽,所以对于阻塞在第三方接口的情况,只能通过主线程来代为执行第三方接口的退出接口;

示例代码

backendTask.h

#ifndef BACKENDTASK_H
#define BACKENDTASK_H

#include <QObject>
#include <QDebug>

// 模拟第3方库接口,不可修改。
class ThirdModule
{
public:
    // 启动服务,阻塞。
    void run()
    {
        while(true)
        {
            if(runFlag)
            {
                // doing work...
            }
            else
                break;
        }
    }

    // 停止服务
    void stop()
    {
        runFlag = false;
    }
private:
    bool runFlag = true;
};

class BackendTask : public QObject
{
    Q_OBJECT
public:
    explicit BackendTask(QObject *parent = nullptr);

    // 退出第三方模块,这是给主线程使用的。
    void stop()
    {
        thirdModule_.stop();
    }

public slots:
    void loop()
    {
        thirdModule_.run();
    }

signals:

private:
    ThirdModule thirdModule_;
};

#endif // BACKENDTASK_H

backendTask.cpp

#include "backendtask.h"

BackendTask::BackendTask(QObject *parent)
    : QObject{parent}
{}

exitQThreadNormally.h

#ifndef EXITQTHREADNORMALLY_H
#define EXITQTHREADNORMALLY_H

#include <QMainWindow>
#include <QThread>
#include "backendtask.h"

QT_BEGIN_NAMESPACE
namespace Ui {
class ExitQThreadNormally;
}
QT_END_NAMESPACE

class ExitQThreadNormally : public QMainWindow
{
    Q_OBJECT

public:
    ExitQThreadNormally(QWidget *parent = nullptr);
    ~ExitQThreadNormally();

private:
    Ui::ExitQThreadNormally *ui;

    BackendTask backendTask_;  //后台服务
    QThread backendTaskThread_;//后台服务:线程
};
#endif // EXITQTHREADNORMALLY_H

exitqthreadnormally.cpp

#include "exitqthreadnormally.h"
#include "ui_exitqthreadnormally.h"

ExitQThreadNormally::ExitQThreadNormally(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::ExitQThreadNormally)
{
    ui->setupUi(this);

    // 1,绑定线程回调
    connect(&backendTaskThread_, &QThread::started, &backendTask_, &BackendTask::loop);
    // 2,将业务对象移动到业务线程
    backendTask_.moveToThread(&backendTaskThread_);
    // 3,启动业务线程(基于QEventloop的信号槽机制,槽函数的执行将被阻塞而无法执行)
    backendTaskThread_.start();
}

ExitQThreadNormally::~ExitQThreadNormally()
{
    delete ui;

    // 清理后台线程资源:
    // 1,当线程阻塞在业务循环时,调用quit()是无效的。
    backendTask_.stop();//2,执行退出业务循环,此时QEventloop机制还是可用的,即槽函数的执行将不被业务循环所阻塞。
    backendTaskThread_.quit();//3,退出线程,回收线程资源,整个线程将不可用,包括其QEventloop。
    backendTaskThread_.wait();//4,与主线程会合,保证资源生命期。
}

main.cpp

#include "exitqthreadnormally.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    ExitQThreadNormally w;
    w.show();
    return a.exec();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值