【Qt线程-2】事件循环(QCoreApplication::processEvents,exec)的应用

2023-05-16

背景:

个人学习多线程控制,写了一些博文用于记录。

【Qt线程-1】this,volatile,exec(),moveToThread()

【Qt线程-3】使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作

【Qt线程-4】事件循环嵌套,BlockingQueuedConnection与QWaitCondition比较

【Qt线程-5】生产者&消费者模型应用(多态,子线程控制,协同,事件循环)

以前用vb或者c#的时候,需要响应哪个事件,就可以添加响应函数来做相应处理。vc++的mfc也是如此。但是它内部是如何实现的,即时暂时不知道,大多数情况下也没有影响做项目。但是在qt中,消息和槽成为了比较通用的概念,很多时候更善于利用它进行参数传递。但事实上,消息和槽机制,离不开事件循环。

概念:

所谓事件循环,就是循环执行消息响应,此时一旦消息队列有消息发生,就可以马上执行槽响应函数。

场景——main函数:

就如常见的main函数,主窗体打开后,后面有个exec(),这就是事件循环。可以理解为一个死循环,永远在等待消息队列。所以,在窗体中放个控件,才可以有槽函数来处理信号响应。

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();//事件循环
}

如果没有它,主窗体会闪一下就过去了。比如这样:

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return 0;
    //return a.exec();
}

像这样main函数以很快的速度创建一个窗体并显示一下,然后又顺理成章地返回0,主进程随着main函数的结束而结束,窗体只是闪一下,什么都干不了。

场景——run函数:

写多线程的时候,可以从QThread派生一个线程类,而且要重写run函数。重写run函数的时候,底部要加一个exec()事件循环。

void MyThread::run()
{
    //希望在线程中完成的操作...

    exec();//事件循环
}

跟main函数一样,这里必须写事件循环,否则run函数执行完前面代码之后会直接结束,线程就结束了。

这里穿插一个概念,所谓线程,不是new了一个线程对象就是线程,这个线程对象其实是在父线程中,跟其它对象一样,new了一个实例而已。它仅仅存在于父线程,它可以作为控制线程的句柄。而真正的线程过程,是run函数启动以后,写在run函数中的代码。所以使用继承QThread并重写run函数的方式实现线程时,一定切记,不是所有函数就一定会在线程中执行,除非它被run函数调用,或者在run当中使用rambda写匿名槽函数。而写匿名槽函数的时候,接收者千万别写this,this指针是指向父线程的线程对象,能作为句柄控制线程,但this隶属于父线程。所以一旦写了this是接收者,这个匿名槽函数会在父线程执行。而子线程中创建对象时,也不要指定parent为this。

所以,在run函数中写exec,它会阻止run函数结束,让子线程始终等待消息队列的任务,从而实现利用信号槽进行线程通信。

场景——子线程对象

上面说过线程的实现,离不开父线程的线程对象,它仅仅是子线程的操作句柄。如果一定要让一个槽函数运行于子线程中,可能还少不了要写个对象再使用movetothread让它进入线程。所以,个人认为,写线程的时候,更好的方法是不要继承QThread并重写run函数。而是把要执行的逻辑写成一个类,实例化以后movetothread,可以确保它一定是在子线程中运行。

MyObject *obj = new MyObject;
QThread *thd = new QThread;
obj->setParent(NULL);
obj->moveToThread(thd);
thd->start();

如果是不太繁忙的工作,可能不需要考虑下面的问题。但我写了一个模拟生产者的逻辑,使用了while循环,这中间需要写sleep来让出cpu资源。比如下面代码中有个变量m_bStop作为标记用来停止工作流程,但是这个变量什么时候生效?就要sleep之后,它才有机会被外部线程修改并生效。

while (!m_bStop)
{
    //Do something

    //这是必须的,否则当前线程无休止循环,根本没有机会让m_bStop变量被外部更改。
    //可以用qDebug在sleep前后分别输出一下m_bStop的值,立见分晓。
    QThread::msleep(500);
}

有个m_bStop变量用于控制退出循环从而结束工作流程。如果把m_bStop声明成public volatile,这样从线程外面可以控制线程中的流程什么时候结束。

如果有些参数需要调整,其实也可以用这种方式。如果想使用信号和槽,上面的写法行不通的。虽然sleep出让了cpu资源,它只是阻塞当前线程,并不会让当前线程“休息”来等待响应信号。所以这里还要用事件循环。于是就有了下面的写法。

while (!m_bStopAll)
{
        //do something.
       
        QCoreApplication::processEvents();

        //Release the cup resource a moment.
        QThread::sleep(m_iInterval);
}

说实话,当第一次用这个时,着实费脑子有些场景可能会想不通。如果类似调整参数或者收发消息之类的操作,应该是没问题的。但是这个子线程我是使用一个子窗体启动它的。而我希望一旦用户中途关闭子窗体,这个子线程也应该自动停止并关闭。

所以我在子窗体中加了这些:

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

    //构造函数中加这么一句,或者写在初始化函数中,目的是关闭窗体时候,
    //调用析构函数,因为默认是不调用的。多次踩坑。
    setAttribute(Qt::WA_DeleteOnClose);
}

FrmProducer::~FrmProducer()
{
    //问题一:
    //最初这里使用信号方式通知子线程结束,事实证明不可取。
    //所以直接使用控制变量来作为while循环的跳出条件。
    m_producer->m_bStopAll = true;

    //问题二:
    //释放子线程中的对象资源
    if (nullptr != m_producer)
    {
        m_producer->deleteLater();//in event loop
        m_producer = nullptr;
    }

    //关闭线程并释放线程资源
    if (nullptr != m_thd && m_thd->isRunning())
    {
        m_thd->quit();
        m_thd->wait();
        m_thd->deleteLater();//in event loop
        m_thd = nullptr;
    }

    delete ui;
}

上面的简化代码,大致说明意思即可。

其中释放资源的部分是需要思维清晰并理解深入的。

问题一:

为了通知子线程结束流程,其实最先想到的是发送信号。但线程间通信,信号槽是异步的,亦即发送信号后,不是子线程马上执行槽函数,而是等待本线程先执行完,回到事件循环之后,子线程再执行槽函数。这有个调度顺序问题。

所以一定要考虑清楚,发完信号之后的代码,千万不要提前断了子线程的后路,否则子线程的逻辑必然报错。

问题二:

上面说了要注意发完信号之后的代码,所以我用了deletelater延迟销毁。但这个函数的含义是,等待子线程执行完逻辑并回到它的事件循环后,再执行销毁。这里不注意就尴尬了。

比如之前的代码:

while (!m_bStopAll)
{
        //do something.
       
        //这里是事件循环,也就是执行到这里,父线程的deletelater会生效,
        //然后子线程的整个世界就毁灭了,所有操作必须安全退出,否则必然报错。
        QCoreApplication::processEvents();

        //出让cpu资源,顺便给m_bStopAll这种标记变量一个被外部更改的机会。
        QThread::sleep(m_iInterval);
}

既然事件循环用于信号和槽,也用于deletelater。那它是先执行结束while循环的响应还是deletelater呢?事件循环的帮助中说明,调用事件循环函数,它会执行完队列中的所有该响应的信号。所以分析一下过程:

如果发送停止信号给子线程,然后马上写了deletelater来清理资源。它执行到事件循环时一定会先响应停止信号,并赋予标记变量m_bStop=true。然后马上清理资源。这就真尴尬了。

因为设置m_bStop=true是希望下一轮while时被检测然后退出while循环。但事实上还不等到下一轮,deletelater就被执行,子线程的世界在while循环停止前提前崩塌了,所以while依然会执行,只是之前的标记变量已经随着子线程的销毁而销毁,它的值已经不准了,所以极有可能造成无人看守的“野循环”。就好像野指针,这显然是不行的。

解决:

所以我上面的方法是:

FrmProducer::~FrmProducer()
{
    //直接使用控制变量来作为while循环的跳出条件。
    m_producer->m_bStopAll = true;
  
    //释放子线程中的对象资源
    if (nullptr != m_producer)
    {
        m_producer->deleteLater();//in event loop
        m_producer = nullptr;
    }

    //关闭线程并释放线程资源
    if (nullptr != m_thd && m_thd->isRunning())
    {
        m_thd->quit();
        m_thd->wait();
        m_thd->deleteLater();//in event loop
        m_thd = nullptr;
    }

    delete ui;
}

子线程也要调整:

while (!m_bStopAll)
{       
    //在事件循环之前检测m_bStopAll这种标记变量,
    //这时候它不会因为deletelater销毁子线程后而无效

    //事件循环用于响应信号和deletelater
    QCoreApplication::processEvents();

    //耗时工作放在这里。

    //出让cpu资源,顺便给m_bStopAll这种标记变量一个被外部更改的机会。
    QThread::sleep(m_iInterval);
}

这就没问题了。用于跳出while循环的标记变量,一定要在生效的时候去判断,所以一定要写在事件循环之前。

而这种标记变量的值,一定是在sleep的时候才有机会被外部更改。

所以上面的while的结束过程是这样的:

之前N轮循环完成工作。

sleep后外部改变m_bStop的值,标记生效。

下一轮开始while(m_bStop),循环退出。

--------------------------

然后就没有然后了,它内部写的那句事件循环
QCoreApplication::processEvents();
已经没机会执行。

因为while循环跳出后,子线程对象执行完毕,
回到了QThread的run函数中默认的exec()事件循环。
所以依然会执行资源清理:

    if (nullptr != m_worker)
    {
        m_worker->deleteLater();//in event loop
        m_worker = nullptr;
    }

    if (nullptr != m_thd && m_thd->isRunning())
    {
        m_thd->quit();
        m_thd->wait();
        m_thd->deleteLater();//in event loop
        m_thd = nullptr;
    }

从而,子线程工作对象中的QCoreApplication::processEvents();
已经不能导致deletelater在不恰当的时机发生。

所以,本人连续踩坑之后,发现还是自己理解不够深入,要说不懂也不是,但缺乏推敲,真的认真看了说明,也就想通了。

其实还有另外的场景和方法:

【Qt线程-3】使用事件循环,信号,stop变量,sleep阻塞,QWaitCondition+QMutex条件变量,退出子线程工作_大橘的博客-CSDN博客

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Qt线程-2】事件循环(QCoreApplication::processEvents,exec)的应用 的相关文章

  • C - 在多线程进程中 exec 是否必须立即跟随 fork ?

    情况 我有一个用 C 编写的多线程程序 如果其中一个线程分叉 则使用 exec 将子进程替换为另一个线程 并且父进程等待子进程退出 Problem 通过 fork 创建子进程后 有几行代码编译要在以下 exec 命令中使用的参数 假设我是否
  • 管道、dup2 和 exec()

    我必须编写一个可以运行管道的外壳 例如像这样的命令ls l wc l 我已经成功解析了用户给出的命令 如下所示 ls 第一个cmd l frsarg wc scmd l secarg 现在我必须使用两个叉子 因为命令是两个和一个管道 我编写
  • 使用字符串输入和输出运行进程

    这里有很多与 fork 和 exec 相关的问题 不过 我还没有找到真正使使用它们的过程变得简单的方法 而让程序员的生活变得简单就是目标 我需要一个 C Linux 友好的函数来执行以下操作 string RunCommand string
  • 生成和执行之间有什么区别?

    我正在学习编写 TCL 期望 脚本 我注意到一些示例显示了如何使用 spawn 而其他示例则显示了命令 exec 我尝试谷歌搜索 但找不到有什么区别 假设我在一个很长的期望脚本中间调用 exec 我会期望发生什么 spawn is an e
  • fork()如何知道自己是在子进程还是在父进程? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 当执行 fork 系统调用时 处理器转入内核模式 因此 在 fork 调用结束时 会生成一个新进程 其中包含调用进程的几乎所有结构的副
  • 运行程序后留下交互式 shell 来使用

    我想通过 shell 运行作为参数给出的任何程序 希望该 shell 保留为交互式 shell 以便稍后使用 bin bash bash i lt
  • 在子进程中调用 execlp() 后如何 printf() ?

    我目前正在尝试在子进程中调用 execlp 后从子进程打印消息 但是 调用 execlp 后 终端上没有出现任何内容 是什么导致我的 printf 调用不显示任何内容 如何解决这个问题 成功后execlp http linux die ne
  • Ruby,exec、system 和 %x() 或反引号之间的区别

    以下 Ruby 方法有什么区别 exec system and x or 反引号 我知道它们用于通过 Ruby 以编程方式执行终端命令 但我想知道为什么有三种不同的方法来执行此操作 system The system http www ru
  • php shell exec wget 不在后台运行

    我想按如下方式运行 wget shell exec wget http somedomain com somefile mp4 sleep 20 continue my code 我想要的是让 PHP 等待 shell exec wget
  • 在golang中一起执行bash echo和nc [重复]

    这个问题在这里已经有答案了 这可能是一个简单的问题 在 Linux 机器上工作 我正在尝试从go程序 我有一个服务器正在监听请求 但这行代码给我带来了问题 cmd exec Command echo n hello nc localhost
  • 了解 bash 中的 exec

    看完之后解释 exec 内置函数如何在 bash 中工作 https stackoverflow com questions 18351198 what are the uses of the exec command in shell s
  • 在 C 客户端服务器应用程序中,socket() 返回 0

    我正在开发一个应用程序 其中包含多个服务器套接字 每个服务器套接字都在唯一的线程中运行 外部实用程序 脚本 由线程之一调用 该脚本调用一个实用程序 客户端 该实用程序将消息发送到服务器套接字之一 最初 我使用的是system 来执行这个外部
  • Execvp 不会对未知命令返回错误

    我有以下代码 用于分叉子级并执行命令 a 这是一个未知命令 但是 execvp 不会返回错误 而是打印 成功 当文件 a 不存在时 如果我执行 mv a b 也会发生同样的事情 我应该如何捕获并处理这些错误 int main int arg
  • 对文件夹中的所有文件执行命令的脚本

    我是 Freebsd nginx 的新手 所以类似的 QA 对我没有帮助 我正在运行一段代码 ext pathinfo FILES rawexcel name i PATHINFO EXTENSION get extension of fi
  • 如何使用 msbuild 获取 exec 任务输出

    我试图通过 exec 任务获得简单的输出msbuild
  • 如何执行脚本从nodejs设置iterm2徽章?

    我从 Iterm2 官方网站获得这个 bash 脚本 printf e 1337 SetBadgeFormat s a echo text base64 我尝试像下面这样执行 没有错误 但无法设置 iterm2 Badge var exec
  • PHP同时运行多个脚本

    我有一个带有对象服务器的数组 如下所示 Array 0 id gt 1 version gt 1 server addr gt 192 168 5 210 server name gt server1 1 id gt 2 server ad
  • php exec() 返回空值

    目前我的目标是使用查看 PHP exec 的输出 但得到一个空值 我正在使用 firephp firebug 扩展 日志记录 但无法弄清楚为什么它是空的 完整代码在这里 https github com MattMcFarland ninj
  • 解决 emacs 错误的想法:“应用:生成子进程:exec 格式错误”

    我正在尝试将 rdebug 与 emacs 和 cygwin 一起使用 但遇到了麻烦 每当我执行 M x rdebug 并为其提供适当的脚本来运行时 它都会因错误而停止 apply Spawning child process exec f
  • 如何执行带有参数的命令?

    如何在 Java 中执行带有参数的命令 我试过了 Process p Runtime getRuntime exec new String php var www script php m 2 这是行不通的 String options n

随机推荐

  • 编译OpenCV 4.7.0 无法解析的外部符号 cv::xfeatures2d::VGG::getDefaultName 问题解决

    最近做特征匹配 xff0c 需要用到xfeatures2d中的特征 xff0c 源码编译OpenCV 4 7 0及opencv contrib 4 7 0中的xfeatures2d模块 xff0c 在Visual Studio 2019中编
  • 记一次在Taro开发的微信小程序中使用lottie动画的经验

    前景提要 最近在做公司项目的时候 xff0c 看到移动端开发用的小图标有动态效果 xff0c 非常好玩了解到是使用lottie进行实现的 xff0c 这个东西以前有看到过对应的插件库 xff0c 但是一直没有时间做研究 xff0c 趁着这个
  • JDB调试Android程序(通过JDB进行代码注入)

    前言 最近在做一些安卓安全相关的事情 xff0c 就看到了一个通过动态调试进行代码注入的一个概念 xff0c 收益匪浅 xff0c 原来好多东西还能这么玩的 闲言少絮 xff0c 开始正式行动 漏洞检查 由于我这边是做的关于安卓安全相关的事
  • [2019.12.20]strncpy发生stack corruption detected(-fstack-protector)栈溢出

    代码 char line MAX 61 0 strncpy line pBeginObj ptemp pBeginObj 43 1 log如下 解释 char strncpy char dest const char src int n 把
  • libmng.so.1: cannot open shared object file: No such file or directory

    span class token function sudo span span class token function ln span s usr lib x86 64 linux gnu libmng so 2 usr lib x86
  • 企业级数据模型主题域模型划分( IBM-FSDM)

    一 前言 如何构建主题域模型原则是构建企业级数据仓库重要的议题 xff0c 最好的路径就是参照成熟的体系 IBM金融数据模型数据存储模型FSDM xff0c 是金融行业应用极为广泛的数据模型 xff0c 可以作为我们构建企业级数据仓库主题域
  • 关于编程学习上的一些感悟——不忘初心

    序 今天无意中看到以前一起开发过的同学写的技术文章 xff0c 了解到了更多在blog和github以及一些技术交流论坛上面非常活跃 回过头来看看自己 xff0c 好像依然停留在以前的样子 xff0c 似乎与真正在踏实学技术差距好像很大了
  • CentOS下ns-3安装教程

    首先 xff0c 安装ns 3时最好不要使用root权限 xff0c 普通用户安装即可 xff0c 否则后来要找文件会比较麻烦 一 安装依赖软件包 首先安装依赖软件包 根据官网 xff08 https www nsnam org wiki
  • 生产者-消费者模型

    文章来自https github com NieJianJian AndroidNotes xff0c 内容将持续更新 xff0c 欢迎star 一 前言 生产者消费者模式并不是GOF提出的23种设计模式之一 xff0c 23种设计模式都是
  • JAVA 多线程解决高并发、超时线程池耗尽问题

    第一类 问题 项目中遇到了 创建20个固定线程的线程池 在测试环境 多线程如果高并发的调用都没出现问题 但是在实际的项目中 出现了线程池内线程超时等待并将池内的线程耗尽 导致其它的程序走到多线程调用时候出现了执行慢 线程无法执行问题 问题原
  • 31_谈谈你对线程安全的理解?(重点)

    如果这个是面试官直接问你的问题 xff0c 你会怎么回答 xff1f 一个专业的描述是 xff0c 当多个线程访问一个对象时 xff0c 如果不用进行额外的同步控制或其他的协调操作 xff0c 调用这个对象的行为都可以获得正确的结果 xff
  • MariaDB 数据类型

    MariaDB 数据类型 数字数据类型 MariaDB支持的数字数据类型如下 类型描述TINYINT此数据类型表示落入 128到127的有符号范围内的小整数 xff0c 以及0到255的无符号范围 BOOLEAN此数据类型将值0与 fals
  • DBSCAN算法(python代码实现)

    DBSCAN 上次学了kmeans基于划分的方法 xff0c 这次学一个基于密度的聚类算法 xff1a DBSCAN xff08 Density Based Spatial Clustering of Applications with N
  • vs2022(缺少MFC,无法新建项目,控件无法添加事件)的解决

    最近下载安装了最新的vs2022社区版 xff0c 想着把之前的c 43 43 项目能够兼容 xff0c 于是遇到了一些列问题 缺少MFC xff0c 无法新建项目 xff0c 控件无法添加事件 这里首先要吐槽一下 xff1a 也许是我电脑
  • C#多线程加载控件界面卡死的解决

    先听一个故事 xff1a 有一个老板忙不过来 xff0c 于是招一个员工去负责某些事务 这样老板就可以腾出时间处理其它事 后来发现员工干不下去 xff0c 原因是干活需要花费 xff0c 没有老板的认可 xff0c 财务不给批钱 这是原则
  • vs2022账户无法登录的解决

    因为昨天重做系统 xff0c 重装了vs2022 xff0c 又涉及到登录的问题 xff0c 一时想不起来之前怎么解决的了 xff0c 想起来以后决定还是记录下来 我遇到的问题是下面这样的 xff0c 提示脚本错误 xff0c 要求升级最新
  • 使用centos7+bind9构建内网私有dns

    有这样一种场景 xff0c 局域网内有一个为网内用户提供服务的机器 xff0c 我们希望像访问互联网站点一样去访问它 xff0c 而不用记忆ip地址和端口 xff0c 比如在web浏览器地址栏输入http www nx com就可以访问它
  • PowerBuilder制作纸牌游戏

    本文记录的是2001年我大三那年假期制作小游戏的思路 xff0c 希望给在读计算机专业的朋友们一些参考 xff0c 如果还没来得及动手尝试的同学 xff0c 一定要勇于动手 你们一定比我做得更好 制作动力 xff1a 我有一个好朋友 xff
  • 【Qt线程-1】this,volatile,exec(),moveToThread()

    背景 xff1a 个人学习多线程控制 xff0c 写了一些博文用于记录 xff1a Qt线程 2 事件循环 xff08 QCoreApplication processEvents xff0c exec xff09 的应用 Qt线程 3 使
  • 【Qt线程-2】事件循环(QCoreApplication::processEvents,exec)的应用

    背景 xff1a 个人学习多线程控制 xff0c 写了一些博文用于记录 Qt线程 1 this xff0c volatile xff0c exec xff0c moveToThread Qt线程 3 使用事件循环 xff0c 信号 xff0