【QT】解决继承QThread的子线程导致程序无法关闭主线程关闭太快导致子线程中的槽方法未执行

news2024/11/23 11:55:39

背景

使用串口进行通信

  • 一共有三个线程
    • 主线程负责界面的显示
    • 子线程1负责检测当前系统可用的串口
    • 子线程2负责差串口通信

子线程实现

  • 在发生问题的最初,因为要一直检测当前系统的可用线程,所以线程1我使用继承自QThread实现的线程,其中重写run函数,并添加while循环,详见问题1中的代码。发生问题所在
  • 子线程2使用movetoThread实现,问题不再这里出现,略。

Q1: 继承QThread的子线程导致程序无法关闭

源代码

产生错误的代码

  • 子线程的run函数
void Check_Serial_Monitor_Thread::run()
{
    m_odd_serial_list.clear();
    QStringList tmp_str_list;

    while(open_flag){// 不断检测是否有新的可用串口出现
        tmp_str_list.clear();
        // 可以再详细一下,检测链接进来然后发生断开的情况。暂时未实现
        foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
            tmp_str_list += info.portName();
        }
        if(tmp_str_list != m_odd_serial_list){// 更新下拉框
            m_odd_serial_list = tmp_str_list;// 更新之前的保存的数据
            Custom_Tools::Print("Update available serial port!");
            emit Have_Change_Serial(tmp_str_list);
        }
    }
}
  • 主线程构造函数中链接信号与槽
connect(this,&Widget::Stop_Serial_Monitor_Thread,
        m_check_serial_thread,&Check_Serial_Monitor_Thread::Stop_Cur_Thread,
        Qt::QueuedConnection);
  • 子线程中的槽方法
void Check_Serial_Monitor_Thread::Stop_Cur_Thread()
{
    Custom_Tools::Print("Quit Slot");
    open_flag = false;
}
  • 主线程析构函数中发出信号
Widget::~Widget()
{
    emit Stop_Serial_Monitor_Thread();

    // 将串口关闭
    if(ui->operate_serial_switch_btn->text() == QString("关闭")){
        Operator_Serial_Switch();
    }

    m_check_serial_thread->quit();
    m_check_serial_thread->wait();

    m_serial_comm_thread.quit();
    m_serial_comm_thread.wait();

    delete ui;
}

问题产生

  • 关闭主窗口后,发现程序并未退出。

错误解析 & 心路历程

原因猜测

  • 我一开始想的是,既然是不同线程,为了线程安全,那我使用第五个参数,指明Qt::QueuedConnection。
    • 发现程序卡死,于是想是不是没有开启事件循环?尝试在子线程run函数中开启后,依然无效,难道说,其实这个对象是属于主线程?
      • 即发生器和接收器在同一个线程中
      • 找到的相似的问题
        • Qt::QueuedConnection not calling receiver thread event loop.
        • QObject based class has a queued connection to itself

事实证明,我的猜想是正确的。

  • 我将第五个参数改为了Qt::BlockingQueuedConnection
    // 关闭串口检测线程的信号
    connect(this,&Widget::Stop_Serial_Monitor_Thread,
            m_check_serial_thread,&Check_Serial_Monitor_Thread::Stop_Cur_Thread,
            Qt::BlockingQueuedConnection);
  • 果不其然,程序发生了死锁,正如官方文档中所说。

Qt::BlockingQueuedConnection
Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.

与 Qt::QueuedConnection 相同,除了信号线程阻塞直到槽返回。 如果接收器位于信号线程中,则不得使用此连接,否则应用程序将死锁。

  • 也就是说,如果添加第五个参数,指定的槽方法执行方式,还是对于主线程来说的。因为这的对象属于主线程。
    • 事件循环,以及事件这个机制是对于线程来说的,而不是对象。
      - 补充: Per-Thread Event Loop
  • OK,现在问题很明确了,为什么这个子线程退不出去?
    • 就是因为run函数中的while(open_flag)没有被更改为false从而终止循环。
      • 为什么没被更改?
        • 因为我们的信号对应的槽函数没有被执行?
      • 为什么没被执行?
        • 因为使用参数Qt::QueuedConnection被放到了主线程的事件队列中,等待当前代码执行完毕之后被执行.

解决方式

  • 在该发送信号后手动调用事件处理。即,先处理这个。
  • 或者去掉Qt::QueuedConnection。
emit Stop_Serial_Monitor_Thread();
QApplication::processEvents();
  • 因为上面run函数没有被终止,进一步导致下方wait函数阻塞,使程序无法终止。
  • 对于quit函数来说——void QThread::quit()

Tells the thread’s event loop to exit with return code 0 (success). Equivalent to calling QThread::exit(0).
This function does nothing if the thread does not have an event loop.

告诉线程的事件循环退出,返回代码为0(成功)。相当于调用QThread::exit(0)。
如果线程没有事件循环,此函数将不执行任何操作。

  • 对于wait函数来说—— bool QThread::wait(QDeadlineTimer deadline = QDeadlineTimer(QDeadlineTimer::Forever))

Blocks the thread until either of these conditions is met:
The thread associated with this QThread object has finished execution (i.e. when it returns from run()). This function will return true if the thread has finished. It also returns true if the thread has not been started yet.
The deadline is reached. This function will return false if the deadline is reached.
A deadline timer set to QDeadlineTimer::Forever (the default) will never time out: in this case, the function only returns when the thread returns from run() or if the thread has not yet started.
This provides similar functionality to the POSIX pthread_join() function.

阻塞线程,直到满足以下任一条件:
与此QThread对象关联的线程已完成执行(即,当它从run()返回时)。如果线程已完成,此函数将返回true。如果线程还没有启动,它也会返回true。
截止日期已到。如果达到截止日期,此函数将返回false。

  • 那么对于继承自QThread实现的线程来说,重写run函数,当并未开启事件循环时,如上面的代码所示,当run函数结束后,线程已经结束了(我是这么认为的)。
    • 加上实际上我们并没有事件循环,quit也不会进行任何操作。
      • 否则,貌似会给当前线程添加一个终止事件,当事件循环执行到这个时,退出循环并结束线程。
        • 详见评论区——How to stop a qThread in QT [duplicate]

结束

  • 至此,导致该程序无法正常退出的问题已经解决,但是,也只是可以让程序正常退出,从我们程序的目的来看,还是要使用moveToThread来创建子线程。
    • 使得我们的子线程具有更多的功能,比如——信号与槽。将某些东西让其在子线程中运行。

Q2:主线程关闭太快导致子线程中的槽方法未执行

背景

  • 我将Q1中出现问题的线程重写,采用moveToThread的方法将对应移动到子线程中,在子线程中开启一个定时器,超时就去检测可用串口。
  • 同样在主线程的析构函数中发出信号,对应的槽方法为停止这个子线程中的定时器。

问题产生

  • 程序可以退出,但是发现对应的子线程中的槽方法并未执行。

错误解析

这里感谢下韬哥,带着我一起调试,解决了这个困扰了我几天的问题。

涉及到的代码

  • 主线程析构函数
Widget::~Widget()
{

    emit Stop_Serial_Monitor_Thread();

    // 终止串口检测线程信号
    // 将串口关闭
    if(ui->operate_serial_switch_btn->text() == QString("关闭")){
        Operator_Serial_Switch();
    }

    m_check_serial_thread.quit();
    m_check_serial_thread.wait();

    m_serial_comm_thread.quit();
    m_serial_comm_thread.wait();

    delete ui;
}
  • 子线程中对应的槽方法
void Check_Serial_Monitor_Worker::Stop_Cur_Thread()
{
    Custom_Tools::Print("Check Serial Stop");
    m_timer->stop();
}

解决

  • 在析构函数中,在该信号发送后,Sleep阻塞主线程一下,让他结束慢点,发现该槽方法成功调用。
  • 或者,connect中使用参数**Qt::BlockingQueuedConnection,**使其在该槽方法执行完毕前,阻塞主线程,直到子线程对应槽方法执行完毕后返回。

补充

  • 总结时发现,调试的时候也可以通过检测这个finished信号,看时间循环时什么时候关闭的。

  • 更多的Qt线程内容补充
    • QThread::Detailed Description
  • Event Loop
    • Threads Events QObjects
  • 貌似,一般来说,不需要手动指明connect的第五个参数,Qt会根据情况自动的选取。是否是在一个线程。
  • 补充内容 & 鸣谢
    • Qt5.9学习笔记5-多线程和通信
    • 【Qt线程-4】事件循环嵌套,BlockingQueuedConnection与QWaitCondition比较

结束语

  • 其实我省略了一些过程内容,并在总结时整合了部分内容,文章中涉及到的一些细节,我可能还没有细挖,感兴趣的小伙伴可以自行查阅资料,有好的内容可以告诉我。
  • 有错误内容还请及时告诉我,希望能帮助到有需要的小伙伴。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/624768.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

github下载的项目如何上传到gitee

1、安装git 省略 2、github下载项目 如何注册、登录省略 3、上传到gitee 注册账号 省略 新建仓库 iot-plat创建过了,用iot-plat1演示 创建完的仓库 此处的https地址要记住,等会要用到 到本地项目目录 项目里面 Git命令操作 空白处右键&#xff…

CSP-S 第一轮笔试重点题

CSP-S提高组笔试题重点题汇总: 今天我给大家分享一些 CSP-S 第一轮笔试中的一些重点题,包含讲解。 第一题: 1.十进制小数13.375对应的二进制数是()。 A.1101.011 B.1011.011 C.1101.101 D.1010.01 解析&#x…

一起学SF框架系列5.3-模块Beans-bean与Spring容器的交互方式

正常情况下,应用中的bean同spring容器关系如下图: 尽管应用bean是Spring容器创建并建立依赖关系,应用只需使用bean即可,因此对bean来说Spring容器就是无感知的(无侵入编程)。但是还是存在需求需要应用bea…

OkHttp 框架设计剖析(含面试题)

作者:Calculus_小王 概述 OKHttp是一个基于HTTP协议的网络请求框架,它支持HTTP/2协议,连接复用和连接池,缓存策略等功能。它的核心设计是拦截器(Interceptor),它将请求的复杂逻辑切分成多个独立…

详解Java内部类、匿名内部类

内部类 内部类:类的第五个成员 1.定义:Java中允许将一个类A声明在另一个类B中,则类A就是内部类,类B称为外部类. 2.内部类的分类: 成员内部类(静态、非静态 ) vs 局部内部类(方法内、代码块内、…

全网最强总结,Selenium自动化测试异常+处理总结,吐血整理...

目录:导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结(尾部小惊喜) 前言 当测试工程师执行…

LVS负载均衡群集部署——DR模式

LVS负载均衡群集部署——DR模式 一、LVS-DR集群概述1、LVS-DR 工作原理2、LVS-DR数据包流量分析(同一局域网)3、LVS-DR中的ARP问题4、LVS-DR处理问题后的流量分析5、LVS-DR 特性 二、构建LVS-DR集群1、构建LVS-DR集群的步骤(理论)…

网工内推 | 网安专场,CISP认证优先,带薪年假,六险一金

01 MATRIX TECH 🔷招聘岗位:安全工程师 🔷职责描述: 1、负责信息安全防护系统的日常管理 、监测和优化提升等安全运营工作,包括终端安全、防护、漏洞检测、补丁、入侵检测、拒绝服务攻击防护、源代码安全检查等&#…

cool-admin框架后端使用-node版本,线上宝塔部署

版本6.x 宝塔新建一个文件夹和创建好数据库,记录账号和密码,自行创建,不做说明 特别注意,如果用宝塔node管理那里运行,如果按照到有pm2的,要先卸载,不可以共存,会有冲突 cool-vue前端…

.gitignore忽略文件不生效

前言 .gitignore忽略文件时git仓库很重要的一个配置,在创建仓库时就会有模板选择和忽略文件。 .gitignore忽略文件意思是在上传到代码仓库时,控制把哪些代码文件不上传到代码仓库。 在实际开发中其实写的代码是没有多大的,主要的是插件本地…

凸优化系列——最优化问题

1. 凸优化问题介绍 凸优化问题如下: 为什么要求不等式约束是线性函数呢?我们知道凸函数的下水平集是凸集。 为什么要求等式约束是线性的呢?线性函数表示一个超平面,他也是凸集 也就是说,对于凸优化问题,…

《Lua程序设计》--学习4

闭包 在Lua语言中,函数是严格遵循词法定界(lexicalscoping)的第一类值(first-classvalue)。 “第一类值”意味着Lua语言中的函数与其他常见类型的值(例如数值和字符串)具有同等权限&#xff1…

Proteus仿真之LCD1602

1.项目简介:利用Proteus仿真在LCD1602上显示字母。 2.设计思路:首先要读懂LCD1602的时序图和每一个端口高低电平时的含义。 然后,通过操作的端口的高低电平来达到操作数据的目的。主要思路是,根据端口的组合来,将数据…

信号原理解析

目录 一、什么是信号 举例子: 进程如何认识信号 信号与进程的异步 进程如何储存信号 二、一个实例 signal函数: 三、实例后的思考 一个进程接受到信号后,处理信号的方法: myhandler什么时候才会被调用 四、理解ctrlc被…

【spring源码系列-04】注解方式启动spring时refresh的前置工作

Spring源码系列整体栏目 内容链接地址【一】spring源码整体概述https://blog.csdn.net/zhenghuishengq/article/details/130940885【二】通过refresh方法剖析IOC的整体流程https://blog.csdn.net/zhenghuishengq/article/details/131003428【三】xml配置文件启动spring时refres…

第五十回:TabBarView Widget

文章目录 概念介绍使用方法示例代码综合使用 我们在上一章回中介绍了DefaultTabBarController Widget相关的内容,本章回中将介绍 TabBarView Widget.闲话休提,让我们一起Talk Flutter吧。 概念介绍 我们这里介绍的TabBarView类似前面章回中介绍过的PageView组件&a…

应对数据不平衡和过拟合的分类模型优化策略

不平衡分类 数据类别不平衡问题是指数据集中各类别样本数量不对等的情况。 基于抽样的方法 在处理这类问题时,可以采用基于抽样的方法来解决。以下是几种常见的基于抽样的方法: 两阶段学习 两阶段学习是一种解决不平衡分类问题的方法,包括…

软件测试 之Web项目实战解析(附全套实战项目教程+视频+源码)

软件测试之web项目实战 按顺序依次为:【搭建测试环境】、【需求评审】、【编写测试计划】、【分析测试点.编写测试用例】、【用例评审】、【执行用例提bug】、【测试报告】 一:搭建测试环境 (1) 搭建测试环境之 【常见项目结构模式】 (2&am…

【大数据之路3】分布式协调系统 Zookeeper

3. 分布式协调系统 Zookeeper 1. Zookeeper 概述1. Zookeeper 介绍2. Zookeeper 结构/功能【重点】1. 文件系统 ZNode1. ZNode 特点2. ZNode 功能3. ZNode 介绍【非常重要】 2. 监听机制 3. 典型应用场景1. 命名服务2. 配置管理3. 集群管理4. 分布式锁5. 队列管理 2. 架构与原理…

MaskRCNN与注意力机制

Mask RCNN---two stage mask rcnn是一个分割算法(实例分割),可用于: 目标检测 实例分割 关键点检测 本质上,mask R-CNN是在faster rcnn的基础上,加入了FCN模块,得到最终的分割结果。 先检测,再分割。不…