当前位置: 首页 > 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();
}

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

http://www.xdnf.cn/news/502201.html

相关文章:

  • MySQL 查询执行流程全解析
  • IPD推行成功的核心要素(二十二)IPD流程持续优化性地推出具备商业成功潜力的产品与解决方案
  • 使用HtmlAgilityPack采集墨迹天气中的天气数据
  • 9.DMA
  • 如果丝杆有轴向窜动应如何处理?
  • 西门子 Teamcenter13 Eclipse RCP 开发 1.3 工具栏 单选按钮
  • 使用tensorRT10部署低光照补偿模型
  • 六、绘制图片
  • traceroute命令: -g与-i 参数
  • flutter长列表 ListView、GridView、SingleChildScrollView、CustomScrollView区别
  • 专题四:综合练习(组合问题的决策树与回溯算法)
  • 嘉立创EDA成图:文件管理
  • 【前端基础】11、CSS的属性特性(继承、层叠、元素类型、隐藏元素的四种方式)
  • 【笔记】正弦交流电路的特征量
  • MMDetection环境安装配置
  • 小蜗牛拨号助手用户使用手册
  • STM32中的DMA
  • Python自学笔记3 常见运算符
  • Redis 事务与管道:原理、区别与应用实践
  • 【JDBC】JDBC概述、历史版本及特征
  • 深入解析 React 的 useEffect:从入门到实战
  • (头歌作业)—6.1 葡萄酒评论分析报告(project)
  • DeepSeek超大模型的高效训练策略
  • 数据结构与算法——双向链表
  • 探秘 Java 字节缓冲流:解锁高效 IO 操作的进阶之路
  • Unity 人物模型学习笔记
  • MATLAB2025新功能
  • 开源项目实战学习之YOLO11:12.3 ultralytics-models-sam-encoders.py源码分析
  • gcc/g++常用参数
  • Go 语言的 GMP 模型