当前位置: 首页 > news >正文

Qt做的应用程序无法彻底关闭的问题解析

大家有没有遇到过这样的情况:我们开发的某个应用程序,有时候在点击关闭按钮后,以为程序已经关闭结束,实际上程序进程还在后台运行。
这种有可能是程序中开启了某个子线程,当你关闭的时候,线程还在运行,导致程序看似关闭了,实际由于子线程的运行,并没有停止进程,而是转为在后台运行。
我们模拟一下这种情况。

1.问题描述

应用程序关闭后,去仍然在后台继续运行。

2.复现问题

我们使用QtCreator创建一个桌面应用程序作为测试程序,在“MainWindow”构造函数中启动一个子线程,这个子线程while循环一直运行,用来实时监测下面连接的下位机设备的连接状态。
主程序代码:

//主程序
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle(QString("测试程序"));//开启线程QThreadPool::globalInstance()->setMaxThreadCount(4);ITask *pTask = new DevConnectTask();if(nullptr == pTask){logger()->info() << __FUNCTION__ << ", create MonitorCurrentTask failed!!!";return ;}QThreadPool::globalInstance()->start(pTask);QObject::connect(pTask, SIGNAL(signal_Result(int, bool)), this, SLOT(slotDevConnect(int, bool)), Qt::QueuedConnection);
}

子线程代码:

//头文件
//这里的父类ITask是继承自QObject和QRunnable的自定义的基类,不需要太纠结
class DevConnectTask : public ITask
{Q_OBJECT
public:DevConnectTask();
protected:void run() override;private:bool m_bToolConnected = false;
signals:void signal_Result(int nDev, bool bStatus);};
//cpp文件
//轮询设备连接状态
DevConnectTask::DevConnectTask()
{qRegisterMetaType<QString >("QString");setAutoDelete(true);
}void DevConnectTask::run()
{logger()->info() << __FUNCTION__ << ", DevConnectTask started---------";bool bTool = false;while (1) {//每隔1秒轮询一次,检测设备的连接状态Command::getInstance()->MonitorDevStatus(bTool, bPower);if(bTool != m_bToolConnected){m_bToolConnected = bTool;//状态改变,发送信号emit signal_Result(Dev, m_bToolConnected);}QThread::msleep(1000);}logger()->info() << __FUNCTION__ << ", DevConnectTask task end------------------";
}

首先,上面的代码逻辑上没有问题,主窗体开启后(或者通过按钮启动)启动子线程来监听外设的连接状况;
其次,程序运行上也没有问题,打开程序后能正常创建子线程,并启动子线程来监听外设的状态改变,也能正常收发状态改变的信号;
最后,关闭程序时发现问题,当你点击左上角关闭按钮后,桌面上貌似程序消失了,但当你打开任务管理器查看时,发现程序实际还在后台一直运行着。

3.分析问题

通过调试我们发现是开启的子线程惹的祸(实际用于正式的工程代码,可能需要调试,日志,及不断地屏蔽代码排除才能确认哪块引起);
那么这个子线程为什么会导致程序的进程并没有在关闭时结束呢?
我们需要回忆一下Qt中QThreadPool的工作原理。QThreadPool管理着一组线程,用于执行QRunnable任务。当调用start()时,任务会被添加到队列中,由线程池中的线程执行。默认情况下,全局线程池会在程序退出时等待所有任务完成,但如果任务中有无限循环或长时间运行的操作(本次测试程序,就是用while循环在无限的工作着),可能会导致等待时间过长,甚至无法退出。
在本测试程序中,当关闭程序时,主界面UI已经被关闭,资源已经释放了,但主进程开启的监听线程,由于while循环并没有因为某个预想的卡断将其退出,还一直在工作着,导致主进程也一直无法销毁,所以在任务管理器看到,程序依然在运行着。
这里在补充一些:程序关闭时线程没有正确停止,导致后台继续运行。其他可能的原因有几个:

  • ​任务中没有退出条件​:如果任务是一个无限循环,没有检查终止条件,线程池会一直等待任务完成,导致程序无法退出。
  • 没有正确清理线程池​:可能在关闭事件中没有正确请求线程池停止或等待任务完成。
  • 任务未设置自动删除​:如果QRunnable没有设置setAutoDelete(true),可能导致资源未释放,影响线程池的清理。

4.解决思路

根据上面的问题,我们可能的解决思路有以下几种:

  • 确保QRunnable任务可以被中断​:在任务中添加一个标志位,定期检查是否需要退出。
  • 在程序关闭时触发所有任务的停止标志​:在MainWindow的closeEvent中遍历所有任务,设置停止标志。
  • 正确清理线程池​:使用waitForDone等待任务结束,并设置超时时间,避免无限等待。
  • 处理未自动删除的任务​:确保QRunnable的自动删除开启,避免内存泄漏。

根据以上思路,得出一下解决方案:

  • 我们引入原子变量,通过原子标志位控制任务执行流程,实现可中断的QRunnable任务。
  • 在主窗口添加关闭事件处理,在关闭事件closeEvent中协调线程池关闭;
  • 线程任务启动与管理这块,我们要加上正确的启动方式​:有回应的管理线程任务;

大致流程如下:
在这里插入图片描述

5.代码优化

根据上面的解决方案,我们将代码进行优化:
首先,子线程中,我们加入原子标志“m_stopFlag”以及线程结束的信号“finished()”,并且用原子标志“m_stopFlag”控制线程的run()方法里的while循环,当线程结束时,发送信号“finished()”。
子线程代码:

//头文件
//这里的父类ITask是继承自QObject和QRunnable的自定义的基类,不需要太纠结
class DevConnectTask : public ITask
{Q_OBJECT
public:DevConnectTask();
protected:void run() override;private:bool m_bToolConnected = false;
signals:void signal_Result(int nDev, bool bStatus);public:
//设立原子标志std::atomic<bool> m_stopFlag{false};
signals:
//添加结束信号void finished();
};
//cpp文件
//轮询设备连接状态
DevConnectTask::DevConnectTask()
{qRegisterMetaType<QString >("QString");setAutoDelete(true);
}void DevConnectTask::run()
{logger()->info() << __FUNCTION__ << ", DevConnectTask started---------";bool bTool = false;//while循环收到原子标志控制while (!m_stopFlag.load(std::memory_order_acquire)) {//每隔1秒轮询一次,检测设备的连接状态Command::getInstance()->MonitorDevStatus(bTool, bPower);if(bTool != m_bToolConnected){m_bToolConnected = bTool;//状态改变,发送信号emit signal_Result(Dev, m_bToolConnected);}QThread::msleep(1000);}logger()->info() << __FUNCTION__ << ", DevConnectTask task end------------------";//线程结束时发送结束信号emit finished();
}

主程序中,我们添加一个QList的成员变量“m_activeTasks”原来记录子线程(可能有多个子线程,所以用QList),并且加上线程结束信号“finished()”的响应处理,同时,我们要加上关闭事件“closeEvent()”,用以处理当主程序关闭时,如果有线程在循环里运行,就通过原子标志控制其退出循环,结束线程。
主程序代码:

//主程序
MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle(QString("测试程序"));//开启线程QThreadPool::globalInstance()->setMaxThreadCount(4);ITask *pTask = new DevConnectTask();if(nullptr == pTask){logger()->info() << __FUNCTION__ << ", create MonitorCurrentTask failed!!!";return ;}// 加入跟踪列表,m_activeTasks在头文件定义:QList<ITask*> m_activeTasks;m_activeTasks.append(pTask);QObject::connect(pTask, &ITask::finished, [this, pTask](){// 线程结束信号处理m_activeTasks.removeOne(pTask);logger()->info() << __FUNCTION__ << "线程任务完成";});QThreadPool::globalInstance()->start(pTask);QObject::connect(pTask, SIGNAL(signal_Result(int, bool)), this, SLOT(slotDevConnect(int, bool)), Qt::QueuedConnection);
}
//重写关闭事件
void MainWindow::closeEvent(QCloseEvent *event) {// 设置所有任务的停止标志for(auto task : m_activeTasks){task->m_stopFlag.store(true, std::memory_order_release);}// 清理线程池QThreadPool::globalInstance()->clear();  // 移除未开始的任务QThreadPool::globalInstance()->waitForDone(100);  // 等待最多100ms// 验证线程池状态logger()->error() << __FUNCTION__ << "关闭前,活动线程数:" << QThreadPool::globalInstance()->activeThreadCount();// 必须调用以完成关闭event->accept();
}

至此,经过验证测试,当关闭程序时,所有线程均能一一结束,确保主进程也彻底结束。大家还有什么其他方法,欢迎留言分享。

相关文章:

  • 如何通过交流沟通实现闭环思考模式不断实现自身强效赋能-250517
  • JavaScript基础-作用域链
  • 【和春笋一起学C++】(十四)指针与const
  • cadence安装license manager无法开启,显示并行配置不正确
  • 【C语言练习】047. 理解递归与循环的转换
  • 期望是什么:(无数次的均值,结合概率)21/6=3.5
  • C++---string类
  • 编程日志5.10
  • 向量数据库Qdrant的Collection参数配置说明
  • 【嵌入式项目-MCU代码2】
  • day28 python 类与继承
  • 使用Spring Boot与Spring Security构建安全的RESTful API
  • 二叉树的中序遍历 递归调用的完整展开
  • 完整卸载 Fabric Manager 的方法
  • Python海龟绘图(Turtle Graphics)核心函数和关键要点
  • W5500使用ioLibrary库创建DHCP客户端
  • 生产者 - 消费者模式实现方法整理
  • python + streamlink 下载 vimeo 短视频
  • 【Element UI】表单及其验证规则详细
  • DAY 23 训练
  • 2025吉林市马拉松开跑,用赛道绘制“博物馆之城”动感地图
  • 哪条线路客流最大?哪个站点早高峰人最多?上海地铁一季度客流报告出炉
  • 又一例!易方达基金张坤卸任副总职务,将专注于投资管理工作
  • 俄乌官员即将在土耳其会谈,外交部:支持俄乌开启直接对话
  • 上海虹桥国际咖啡文化节开幕,推出茶咖文化特色街区、宝妈咖啡师培训
  • 郑钦文憾负高芙,止步WTA1000罗马站四强