QT-事件循环机制

2023-11-01

QT事件循环理解

一. 常见问题

问题1:Qt中常见的事件有哪些?

答:鼠标事件(QMouseEvent)、键盘事件(QKeyEvent)、绘制事件(QPaintEvent)、窗口尺寸改变(QResizeEvent)、滚动事件(QScrollEvent)、控件显示(QShowEvent)、控件隐藏(QHideEvent)、定时器事件(QTimerEvent)等。

问题2:Qt是事件驱动的,这句话该怎么理解呢?

Qt将系统产生的信号(软件中断)转换成Qt事件,并且将事件封装成类,所有的事件类都是由QEvent派生的,事件的产生和处理就是Qt程序的主轴,且伴随着整个程序的运行周期。因此我们说,Qt是事件驱动的。

问题3:Qt事件是由谁产生的?

答:Qt的官方手册说,事件有两个来源:程序外部和程序内部,多数情况下来自操作系统并且通过spontaneous()函数返回true来获知事件来自于程序外部,当spontaneous()返回false时说明事件来自于程序内部,就像例程1创建一个事件并把它分发出去。

问题4:Qt事件是由谁接收的?

答:QObject!它是所有Qt类的基类!是Qt对象模型的核心!QObject类的三大核心功能其中之一就是:事件处理。QObject通过event()函数调用获取事件。所有的需要处理事件的类都必须继承自Qobject,可以通过重定义event()函数实现自定义事件处理或者将事件交给父类。

问题5:事件处理的流程是什么样的?

答:事件有别于信号的重要一点:事件是一个类对象具有特定的类型,事件多数情况下是被分发到一个队列中(事件队列),当队列中有事件时就不停的将队列中的事件发送给QObject对象,当队列为空时就阻塞地等待事件,这个过程就是事件循环!

问题6:Qt 事件和信号的关系?

Qt的事件是windows的底层消息封装而成的。这个消息和MFC里的消息是同一概念,都是指键盘、鼠标等的按压、松开等消息。例如按下键盘后,windows系统会发出一个 WM_KEYDOWN的消息,Qt捕获这个消息后,将其转换成 Qt::Key_Down 事件。Qt的事件是较为底层的概念。先有事件,然后才有信号。即:消息 -> 事件 -> 信号。

总结:windows发出消息,Qt捕获消息后转换成事件,再由事件处理后发出信号。

本文福利, 免费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

二.事件循环

那事件循环是什么?   

在说明事件循环之前,先认识两个术语:

可重入的(Reentrant):如果多个线程可以在同一时刻调用一个类的所有函数,并且保证每一次函数调用都引用一个唯一的数据,就称这个类是可重入的大多数C++类都是可重入的。类似的,一个函数被称为可重入的,如果该函数允许多个线程在同一时刻调用,而每一次的调用都只能使用其独有的数据。全局变量就不是函数独有的数据,而是共享的。换句话说,这意味着类或者函数的使用者必须使用某种额外的机制(比如锁)来控制对对象的实例或共享数据的序列化访问。  

线程安全(Thread-safe):如果多个线程可以在同一时刻调用一个类的所有函数,即使每一次函数调用都引用一个共享的数据,就说这个类是线程安全的。如果多个线程可以在同一时刻访问函数的共享数据,就称这个函数是线程安全的。

进一步说,对于一个类,如果不同的实例可以被不同线程同时使用而不受影响,就说这个类是可重入的;如果这个类的所有成员函数都可以被不同线程同时调用而不受影响,即使这些调用针对同一个对象,那么我们就说这个类是线程安全的。由此可以看出,线程安全的语义要强于可重入。接下来,我们从事件开始讨论。之前我们说过,Qt是事件驱动的。在 Qt 中,事件由一个普通对象表示(QEvent或其子类)。这是事件与信号的一个很大区别:事件总是由某一种类型的对象表示,针对某一个特殊的对象,而信号则没有这种目标对象。所有QObject的子类都可以通过覆盖QObject::event()函数来控制事件的对象。

事件可以由程序生成,也可以在程序外部生成。例如:

QKeyEvent和QMouseEvent对象表示键盘或鼠标的交互,通常由系统的窗口管理器产生;
QTimerEvent事件在定时器超时时发送给一个QObject,定时器事件通常由操作系统发出;
QChildEvent在增加或删除子对象时发送给一个QObject,这是由 Qt 应用程序自己发出的。

需要注意的是,与信号不同,事件并不是一产生就被分发。

事件产生之后被加入到一个队列中(先进先出),该队列即被称为事件队列。

事件分发器遍历事件队列,如果发现事件队列中有事件,那么就把这个事件发送给它的目标对象。这个循环被称作事件循环。

QCoreApplication::exec()开启了这种循环,一直到QCoreApplication::exit()被调用才终止,所以说事件循环是伴随着Qt程序的整个运行周期!

三.深度理解事件循环

事件循环一般用exec()函数开启。QApplicaion::exec()、QMessageBox::exec()都是事件循环。其中前者又被称为主事件循环。事件循环首先是一个无限“循环”,程序在exec()里面无限循环,能让跟在exec()后面的代码得不到运行机会,直至程序从exec()跳出。从exec()跳出时,事件循环即被终止。QEventLoop::quit()能够终止事件循环。其次,之所以被称为“事件”循环,是因为它能接收事件,并处理之。当事件太多而不能马上处理完的时候,待处理事件被放在一个“队列”里,称为“事件循环队列”。当事件循环处理完一个事件后,就从“事件循环队列”中取出下一个事件处理之。当事件循环队列为空的时候,它和一个啥事也不做的永真循环有点类似,但是和永真循环不同的是,事件循环不会大量占用CPU资源。事件循环的本质就是以队列的方式再次分配线程时间片。****

事件循环的伪代码描述大致如下所示:

while (is_active)
{
    while (!event_queue_is_empty) {
        dispatch_next_event();
    }
    wait_for_more_events();
}

调用QCoreApplication::exec()函数意味着进入了主循环。我们把事件循环理解为一个无限循环,直到QCoreApplication::exit()或者QCoreApplication::quit()被调用,事件循环才真正退出。

伪代码里面的while会遍历整个事件队列,发送从队列中找到的事件;wait_for_more_events()函数则会阻塞事件循环,直到又有新的事件产生。我们仔细考虑这段代码,在wait_for_more_events()函数所得到的新的事件都应该是由程序外部产生的。(因为所有内部事件都应该在事件队列中处理完毕了。)

在类 UNIX 系统中,窗口管理器(比如X11)会通过套接字(UnixDomain或TCP/IP)向应用程序发出窗口活动的通知,因为客户端就是通过这种机制
与X服务器交互的。如果我们决定要实现基于内部的socketpair函数的跨线程事件的派发,那么窗口的管理活动需要唤醒的是:
套接字 SOCKET、定时器 TIMER
这也正是select系统调用所做的:它监视窗口活动的一组描述符,如果在一定时间内没有活动,它会发出超时消息(这种超时是可配置的)。Qt所要做
的,就是把select()的返回值转换成一个合适的QEvent子类的对象,然后将其放入事件队列。
好了,现在你已经知道事件循环的内部机制了。

因此,我们说事件循环在wait_for_more_events()函数进入休眠,并且可以被下面几种情况唤醒:

窗口管理器的动作(键盘、鼠标按键按下、与窗口交互等); 套接字动作(网络传来可读的数据,或者是套接字非阻塞写等); 定时器; 由其它线程发出的事件。 

 至于为什么需要事件循环,我们可以简单列出一个清单:

组件的绘制与交互:QWidget::paintEvent()会在发出QPaintEvent事件时被调用。该事件可以通过内部QWidget::update()调用或者窗口管理器 (例如显示一个隐藏的窗口)发出。所有交互事件(键盘、鼠标)也是类似的:这些事件都要求有一个事件循环才能发出。 定时器:长话短说,它们会在select或其他类似的调用超时时被发出,因此你需要允许 Qt 通过返回事件循环来实现这些调用。 网络:所有低级网络类(QTcpSocket、QUdpSocket以及QTcpServer等)都是异步的。当你调用read()函数时,它们仅仅返回已可用的数据; 当你调用write()函数时,它们仅仅将写入列入计划列表稍后执行。只有返回事件循环的时候,真正的读写才会执行。注意,这些类也有同步函数 (以waitFor开头的函数),但是它们并不推荐使用,就是因为它们会阻塞事件循环。高级的类,例如QNetworkAccessManager则根本不提供同步 API,因此必须要求事件循环。

有了事件循环,你就会想怎样阻塞它。阻塞它的理由可能有很多,例如我就想让QNetworkAccessManager同步执行。在解释为什么永远不要阻塞事件循环之前,我们要了解究竟什么是“阻塞”。假设我们有一个按钮Button,这个按钮在点击时会发出一个信号。这个信号会与一个Worker对象连接,这个Worker对象会执行很耗时的操作。当点击了按钮之后,我们观察从上到下的函数调用堆栈:

main(int, char **)
QApplication::exec()
[…]
QWidget::event(QEvent *)
Button::mousePressEvent(QMouseEvent *)
Button::clicked()
[…]
Worker::doWork()

我们在main()函数开始事件循环,也就是常见的QApplication::exec()函数。窗口管理器侦测到鼠标点击后,Qt 会发现并将其转换成QMouseEvent事件,发送给组件的event()函数。这一过程是通过 QApplication::notify() 函数实现的。

注意:我们的按钮并没有覆盖event()函数,因此其父类的实现将被执行,也就是QWidget::event()函数。这个函数发现这个事件是一个鼠标点击事件 ,于是调用了对应的事件处理函数,就是Button::mousePressEvent()函数。我们重写了这个函数,发出Button::clicked()信号,而正是这个信 号会调用Worker::doWork()槽函数。

在worker努力工作的时候,事件循环在干什么?或许你已经猜到了答案:什么都没做!事件循环发出了鼠标按下的事件,然后等着事件处理函数返回。此时,它一直是阻塞的,直到Worker::doWork()函数结束。

注意,我们使用了“阻塞”一词,也就是说,所谓阻塞事件循环,意思是没有事件被派发处理。

在事件就此卡住时,组件也不会更新自身(因为QPaintEvent对象还在队列中),也不会有其它什么交互发生(还是同样的原因),定时器也不会超时并且网络交互会越来越慢直到停止。也就是说,前面我们大费周折分析的各种依赖事件循环的活动都会停止。

这时候,需要窗口管理器会检测到你的应用程序不再处理任何事件,于是告诉用户你的程序失去响应。这就是为什么我们需要快速地处理事件,并且尽可能快地返回事件循环。

现在,重点来了:我们不可能避免业务逻辑中的耗时操作,那么怎样做才能既可以执行那些耗时的操作,又不会阻塞事件循环呢?一般会有三种解决方案:

第一,我们将任务移到另外的线程中进行实现

第二,我们手动强制运行事件循环。想要强制运行事件循环,我们需要在耗时的任务中一遍遍地调用QCoreApplication::processEvents()函数。QCoreApplication::processEvents()函数会发出事件队列中的所有事件,并且立即返回到调用者。仔细想一下,我们在这里所做的,就是模拟了一个事件循环。

另外一种解决方案:使用QEventLoop类重新进入新的事件循环。通过调用QEventLoop::exec()函数,我们重新进入新的事件循环,给QEventLoop::quit()槽函数发送信号则退出这个事件循环。举一个网络通信用到的例子来说:

QEventLoop eventLoop;
connect(netWorker, &NetWorker::finished,&eventLoop, &QEventLoop::quit);
QNetworkReply *reply = netWorker->get(url);
replyMap.insert(reply, FetchWeatherInfo);
eventLoop.exec();

因为QNetworkReply没有提供阻塞式API,并且要求有一个事件循环。我们通过一个局部的QEventLoop来达到这一目的:当网络响应完成时,这个局部的事件循环也会退出。

四.QEventLoop 有关函数

1.一般我们的事件循环都是由exec()来开启的,例如下面的例子:

QCoreApplicaton::exec()
 QApplication::exec()
 QDialog::exec()
 QThread::exec()
 QDrag::exec()
 QMenu::exec()

这些都开启了事件循环,事件循环首先是一个无限“循环”,程序在exec()里面无限循环,能让跟在exec()后面的代码得不到运行机会,直至程序从exec()跳出。从exec()跳出时,事件循环即被终止。QEventLoop::quit()能够终止事件循环。

事件循环实际上类似于一个事件队列,对列入的事件依次的进行处理,当时间做完而时间循环没有结束的时候,其实际上比较类似于一个不占用CPU事件的for(;;)循环。(其本质实际上是以队列的方式来重新分配时间片。)

2.事件循环是可以嵌套的,当在子事件循环中的时候,父事件循环中的事件实际上处于中断状态,当子循环跳出exec之后才可以执行父循环中的事件。当然,这不代表在执行子循环的时候,类似父循环中的界面响应会被中断,因为往往子循环中也会有父循环的大部分事件,执行QMessageBox::exec(),QEventLoop::exec()的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是由于GUI界面的响应已经被包含到子循环中了,所以GUI界面依然能够得到响应。

通过“其它的入口”进入事件循环要特别小心:因为它会导致递归调用

3.如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出。 举几个例子吧,比如说如果想要将主线程等待100ms,总不能使用sleep吧,那样会导致GUI界面停止响应的,但是用事件循环就可以避免这一点:

 QEventLoop loop;
 QTimer::singleShot(100, &loop, SLOT(quit())); // 在一个给定时间间隔msec(毫秒)之后调用一个槽
 loop.exec();

还有,比如说对于一个槽函数,触发之后会弹出一个dialog,但是像下面这样写的话,窗口会一闪而过的:

 void ****::mySLot{
     QDialog dlg;
     dlg.show();
 }

当然这里可以使用将dlg改成一个静态成员,通过增长期生存期的方法来解决这个问题,但是这里同样可以使用eventLoop来解决这个问题:

 void ****::mySLot{
     QDialog dlg;
     dlg.show();
     QEventLoop loop;
     connect(&dlg, SIGNAL(finished(int)), &loop, SLOT(quit()));
     loop.exec(QEventLoop::ExcludeUserInputEvents);
 }

 本文福利, 免费领取Qt开发学习资料包、技术视频,内容包括(C++语言基础,Qt编程入门,QT信号与槽机制,QT图像绘制,QT网络,QT数据库编程,QT项目实战,QT嵌入式开发,Quick模块等等)↓↓↓↓↓↓见下面↓↓文章底部点击免费领取↓↓

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

QT-事件循环机制 的相关文章

随机推荐

  • Spark一路火花带闪电——Spark底层原理介绍

    文章目录 Spark计算引擎原理 1 1 术语 1 1 1 Application Spark应用程序 1 1 2 Driver 驱动程序 1 1 3 Cluster Manager 资源管理器 1 1 4 Executor 执行器 1 1
  • python---闭包

    1 闭包理解 闭包定义 在函数嵌套的前提下 内部函数使用了外部函数的变量 并且外部函数返回了内部函数 我们把这个使用外部函数变量的内部函数称为闭包 2 必报的构成条件 在函数嵌套 函数里面在定义函数 的前提下 内部函数使用了外部函数的变量
  • 【Kaggle】【Output创建文件夹时“nameError: name ‘mkdir‘ is not defined”】

    文章目录 问题描述 解决方法 Reference 问题描述 想要手动设置Output的文件夹 便于结果保存及分类保存等 在一个cell中输入图2的命令 出现报错 解决方法 cell中不能有多余的东西 一句命令对应一个cell 多余的即使注释
  • 什么是ChatGPT?如何与ChatGPT高效交流?

    ChatGPT是一种基于语言模型的对话系统 由OpenAI开发 它建立在GPT Generative Pre trained Transformer 模型的基础上 在大规模语料库上进行了预训练 并通过与人类进行交互式对话来进行微调 一 什么
  • Python爬虫学多久才能接单?

    要学多久才能掌握爬虫技能并能够接单 这个因人而异 取决于学习的方式 时间和个人天赋 以下是一些学习建议 1 了解基础知识 在开始爬虫之前 确保您对HTML CSS JavaScript以及HTTP等基础编程和网络技术有所了解 2 学习编程语
  • 叶荣添给你的11条投机建议!

    一 刚则易折 至阴至柔是王道 凡事不可勉强股票也一样要学会知难而退 善于变化 既然大的趋势确定了就不必急于一时的涨跌 二 退却不代表放弃 暂时的放弃不代表今生不再回头 此句意味善于止损 在股票操作里风险第一 盈利第二 在操作交易的过程中永远
  • Qt中的窗口类

    文章目录 1 QWidget 1 QWidget 2 QDialog 2 1 QMessageBox 2 2 QFileDialog 2 3 QFontDialog 2 4 QColorDialog 2 5 QInputDialog 2 6
  • 三星被曝因ChatGPT泄露芯片机密!韩媒惊呼数据「原封不动」直传美国,软银已禁止员工使用......

    点击下方卡片 关注 CVer 公众号 AI CV重磅干货 第一时间送达 点击进入 gt 计算机视觉 微信技术交流群 明敏 萧箫 发自 凹非寺转载自 量子位 QbitAI 三星引入ChatGPT不到20天 就发生3起数据外泄事件 其中2次和半
  • 不贴代码能说明白Jetpack LiveData原理吗(一)

    LifecycleOwner如何提供周期生命周期的变化 LifecycleObserver如何得知生命周期的变化 LiveData的背后隐藏了多少不为人知的秘密 这一切都要从观察者模式说起 起源 何为观察者模式 在代码中最直接的表现就是在事
  • Arcgis andoid开发之应用百度地图接口实现精准定位与显示

    怀着激动 兴奋的心情 在这个漫天柳絮的季节写下了这片博文 为什么呢 因为困扰我很久的一个技术性的问题得到了解决 发次博文 供大家参观 学习 同时 也以慰藉我长期困扰的心情 好了 废话不再 言归正传 看看这到底是个什么东西 首先 简单地介绍一
  • 设计模式---适配器模式

    适配器模式 基本介绍 适配器模式 Adapter Pattern 将某个类的接口转换成客户端期望的另一个接口表示 主的目的是兼容性 让原本因接口不匹配不能一起工作的两个类可以协同工作 其别名为包装器 Wrapper 适配器模式属于结构型模式
  • 基于Keras_bert模型的Bert使用与字词预测

    基于Keras bert模型的Bert使用与字词预测 学习参考杨老师的博客 请支持原文 一 Keras bert 基础知识 1 1 kert bert库安装 1 2 Tokenizer文本拆分 1 3 训练和使用 构建模型 模型训练 使用模
  • 计算机二级C语言题库(44套真题+刷题软件)第二套

    刷题软件 gongzhonghao 露露IT 1 某带链栈的初始状态为top bottom NULL 经过一系列正常的入栈与退栈操作后 top bottom 20 该栈中的元素个数为 A 1 B 0 C 20 D 不确定 本题的考查知识点是
  • 【Seaborn】绘图工具的魅力

    文章目录 1 seaborn简介 2 seaborn风格 3 seaborn调色板及颜色设置 4 seaborn绘图方式 1 单变量分析绘图 2 绘制双变量联合分布图 3 多变量关系分布图 1 seaborn简介 Seaborn在 Matp
  • pacemaker+corosync中crm命令用法

    注 本文来自 http www 111cn net sys linux 73074 htm 一 crm有两种工作方式 1 批处理模式 就是在shell命令行中直接输入命令 2 交互式模式 crm live 进入到crmsh中交互执行 二 命
  • DHCP协议的运行过程

    DHCP协议的运行过程 预热知识 DHCP协议是使用C S模式 DHCP服务器运行DHCP服务器进程 在用户主机上运行DHCP客户进程 简称为DHCP客户 DHCP协议是TCP IP应用层的协议 使用的是传输层的UDP所提供的服务 DHCP
  • 2022团体程序设计天梯赛题解 Python

    p1 签到题 print I m gonna win Today print 2022 04 23 p2 L1 2 种钻石 5 分 n v list map int input split print n v p3 L1 3 谁能进图书馆
  • Python三维绘图——Matplotlib

    菜鸡的第一篇博客 学习一下大佬的笔记 1 创建三维坐标轴对象Axes3D 方法一 利用关键字 projection 3D 来实现 方法一 利用关键字 objection 3d from matplotlib import pyplot as
  • JAVA【设计模式】开闭原则

    开闭原则 一 设计模式的规范 二 开闭原则 三 示例 开闭原则设计 UML关系图 一 设计模式的规范 设计模式遵循六 原则 单 职责 个类和 法只做 件事 替换 多态 类可扩展 类 依赖 倒置 细节依赖抽象 下层依赖上层 接 隔离 建 单
  • QT-事件循环机制

    QT事件循环理解 一 常见问题 问题 Qt中常见的事件有哪些 答 鼠标事件 QMouseEvent 键盘事件 QKeyEvent 绘制事件 QPaintEvent 窗口尺寸改变 QResizeEvent 滚动事件 QScrollEvent