测试环境:Win11 & Qt_6_6_1 & Mingw-64
完成时间:2024/3/10
问题描述
当子线程运行在一个阻塞调用上时,主线程退出并尝试回收子线程资源失败。
后台线程创建的一般流程
- 在主线程创建业务线程对象backendTaskThread_,以及被其接管的业务对象backendTask_;
- 线程对象的启动信号 绑定 业务对象业务循环(槽函数);
- 启动业务线程;
后台线程退出的一般流程
- 退出业务循环;
- 退出线程;
- 与主线程会合;
关键逻辑
- 后台线程正常运行期间,线程调度权一直被业务循环所独占,而通过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();
}