Qt事件和事件循环
在处理QT循环事件的时候遇到了问题,查了半天资料都没弄明白问题出在哪,后来找大牛同事问了一下,同事就给我写了QCoreApplication::processEvent()这个函数,好啦,终于搞定了,这里小记一下,以免以后遇到。
于是乎这里认真仔细的看了一下Qt的事件和事件循环。(引用了碎炎的博客)
事件和事件循环
作为一个事件驱动的工具包,事件和事件传递扮演者Qt架构中的中心角色。在本文中我们不会给出一个对这个话题的全面的概述,我们将着眼于一些线程相关的概念。
事件能被程序的内部和外部产生,举个例子:
QKeyEvent和QMouseEvent对象代表了一个键盘和鼠标的事件,它们从窗口由用户的操作而产生。
QTimerEvent对象是当某个时间被激发时投入,它们都由操作系统产生。
QChildEvent对象是当一个子窗口被添加或者移除时候被送入QObject的,它们的源头是Qt程序自己。
事件的重点是它们被产生的时候不会被传递;它们会先进入事件队列,某刻会被传递。传送者自己循环访问事件队列并把事件传递给目标QObject对象,因此称作事件循环。概念上说,时间循环就像这个:
1. while (is_active)
2. {
3. while (!event_queue_is_empty)
4. dispatch_next_event();
5.
6. wait_for_more_events();
7. }
我们通过运行QCoreApplication::exec()来进入消息循环,这个循环直到exit()或者quit()被调用时才会被堵塞,然后退出。
这个”wait_for_more_events()”函数处于堵塞状态,直到有新的事件被产生了。假如我们考虑它,所有在此刻可能产生的事件是外部源头的。因此,这个消息循环能被以下情况唤醒:
窗口管理活动(鼠标按键操作等);
套接字事件;
定时器事件;
其它线程中投递的事件、
在Unix-like系统中,窗口管理器通过套接字来通知应用程序,即使客户端使用它们来与x server通讯。如果我们决定用内部的套接字对去实现跨线程的事件投递,所有剩下的唤醒条件如下:
套接字;
定时器;
这个就是select(2)系统调用所做的;它监视着一系列的一系列的活动者的描述符,如果它们在一定的时间内没有特定的活动,它们就超时了。
一个运行着的事件循环需要什么?
这个不是完整的列表,但是如果你有整体画面,你将能够去猜测什么类需要一个运行着的事件循环。
Widgets的绘画和互动:QWidget::paintEvent()将在传递QPaintEvent对象时候被调用,这个对象将会在调用QWidget::update()或者窗口管理器的时候产生:响应的事件将需要一个时间循环去分发。
Timers:长话短说,它们在当select(2)或者超时的时候产生,因此它们需要让Qt在返回时间循环的时候作这些调用。
Networking“所有的底层Qt网络类 (QTcpSocket,QUdpSocket,QTcpServer等)都是异步设计的。当你调用ready(),它们只是返回已经可用的数据,当你调 用write(),它们只是将这个操作放入队列,适时会写入。只有当你返回消息循环的时候真实的读取,写入才会执行。注意它们确实提供了同步的方法,但是 它们的用法是不被提倡的,因为它们会堵塞事件循环。高级类,比如QNetworkAccessManager,简单的不提供同步API,需要一个事件循 环。
堵塞事件循环
在我们讨论为什么你应该从不堵塞消息循环之前,我们试着分析堵塞的含义。假象你有一个按钮,它将会在它被点击的时候发出clicked信号;在我们的对象中连接着一个槽函数,当你点击了那个按钮后,栈追踪将会像这样:
1. main(int, char **)
2. QApplication::exec()
3. […]
4. QWidget::event(QEvent *)
5. Button::mousePressEvent(QMouseEvent *)
6. Button::clicked()
7. […]
8. Worker::doWork()
在main函数中我们启动了时间循环,平常的调用了 exex(),窗口管理器给我们发送了一个鼠标点击事件,它被Qt内核取走,转换成QMouseEvent并被送往我们widget的event()方 法,该方法被QApplication::notify()发送。因为按钮没有重写event(),基类方法将被调用,QWidget::event() 检测到了这个事件确实是一个鼠标点击事件,然后调用特定的事件处理函数,那就是Button::mousePressEvent(),我们重写这个方法去 发送clicked()信号,这将会调用被连接的槽函数。
当该对象处理量很大,那么消息循环在作什么?我们应该猜测它:什么都不做!它分发鼠标按下事件,然后就堵塞着等待着事件处理函数返回。这个就是堵塞了时间循环,它意味着没有消息被分发了,知道我们从槽函数返回了,然后继续处理挂起的消息。
在消息循环被卡住的情况下,widgets将不能更新它们 自身,不可能有更多的互动,timers将不会被激发,网络通讯将缓慢下来,或者停止。进一步的说,许多窗口管理器将检测到你的应用程序不在处理事件了, 然后告诉用户你的程序没有响应。这就是为什么快速的对事件响应并且即时返回到事件循环是多么的重要!
强制事件分发
所以,假如我们有一个很长的任务去运行但是又不希望堵塞这 个消息循环,该怎么做呢?一个可能的回答是将这个任务移到另一个线程中,在下一个张洁我们将看到这是如何做的。我们也能手动强制事件循环去运行,这个方法 是通过在堵塞的任务函数中调用QCoreApplication::processEvent()来实现 的,QCoreApplication::processEvent()将处理所有在消息队列中的消息并返回给调用者。
另一个可选的选项是我们能够强制重入事件循环的对象,就是QEventLoop类。通过调用QEventLoop::exec()我们将重入事件循环,然后我们能将槽函数QVentLoop::quit()连接到信号上去使它退出。举个例子:
1. QNetworkAccessManager qnam;
2. QNetworkReply *reply = qnam.get(QNetworkRequest(QUrl(...)));
3. QEventLoop loop;
4. QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
5. loop.exec();
6. /* reply has finished, use it */
QNetworkReply不提供堵塞的API,它要求一个在运行的事件循环。我们进入了一个本地的事件循环,然后当回复完成时候,这个本地的循环退出了。
要特别小心的是在其它路径下重入事件循环:它可能导致不希望的递归!让我们回到前面看看按钮的例子。假如我们在槽函数中调用了QCoreApplication::processEvent(),当用户点击了这个按钮,这个槽函数将被再次调用:
1. main(int, char **)
2. QApplication::exec()
3. […]
4. QWidget::event(QEvent *)
5. Button::mousePressEvent(QMouseEvent *)
6. Button::clicked()
7. […]
8. Worker::doWork() // first, inner invocation
9. QCoreApplication::processEvents() // we manually dispatch events and…
10. […]
11. QWidget::event(QEvent * ) // another mouse click is sent to the Button…
12. Button::mousePressEvent(QMouseEvent *)
13. Button::clicked() // which emits clicked() again…
14. […]
15. Worker::doWork() // DANG! we’ve recursed into our slot.
一个快速并简便的变通方法是把QEventLoop::ExcludeUserInputEvent传递给QCoreApplication::processEvents(),这会告诉消息循环不要再次分发任何用户的输入事件。
幸运的是,这个相同的事情不会在检测事件中发生。事实上,它们被Qt通过特殊的方法处理了,只有当运行的时间循环有了一个比deleteLater被调用后更小的”nesting”值才会被处理:
将不会使object成为一个悬空指针。相同的东西被应用 到了本地的事件循环中。唯一的一个显著区别我已经发现了,它在假如当没有事件循环在运行的时候deleteLater被调用了的条件下,然后第一个消息循 环进入了后会取走这个事件,然后删除这个object。这是相当合理的,因为Qt不知道任何外部的循环将最终影响这个检测,因此马上删除了这个 object。
Qt事件和事件循环的更多相关文章
- Qt ------ 再论事件循环
在介绍在以前,我们要认识两个术语: 可重入的(Reentrant):如果多个线程可以在同一时刻调用一个类的所有函数,并且保证每一次函数调用都引用一个唯一的数据,就称这个类是可重入的(Reentrant ...
- 【Qt开发】事件循环与线程 二
事件循环与线程 二 Qt 线程类 Qt对线程的支持已经有很多年了(发布于2000年九月22日的Qt2.2引入了QThread类),Qt 4.0版本的release则对其所有所支持平台默认地是对多线程支 ...
- 【Qt开发】事件循环与线程 一
事件循环与线程 一 初次读到这篇文章,译者感觉如沐春风,深刻体会到原文作者是花了很大功夫来写这篇文章的,文章深入浅出,相信仔细读完原文或下面译文的读者一定会有收获. 由于原文很长,原文作者的行文思路是 ...
- Qt自定义事件的实现(军队真正干活,但要增加监军,大平台通知事件,事件内容自定义)
初学Qt,用了Qt自带的事件,然后想怎么才能定义自己的事件呢?又如何使用自定义事件呢?看了篇文章,说先要子类化QEvent,然后定义自己的QEvent::Type,然后重写QWidget::event ...
- qt中的事件机制
事件 1.QEvent -->类型 -> QKeyEvent QEvent::KeyRelease QEvent::MouseMove -> QMouseEvent 2.事件处理过程 ...
- 【转】Qt鼠标键盘事件
http://blog.csdn.net/lovebird_27/article/details/50351336 Qt 程序需要在main()函数创建一个QCoreApplication对象,然后调 ...
- Qt消息机制和事件、事件过滤
一,事件 事件(event)是由系统或者 Qt 本身在不同的时刻发出的.当用户按下鼠标.敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件.一些事件在对用户操作做出响应时发出,如键盘事件等 ...
- QT父子窗口事件传递与事件过滤器(讲了一些原理,比较清楚)
处理监控系统的时候遇到问题,在MainWidget中创建多个子Widget的时候,原意是想鼠标点击先让MainWidget截获处理后再分派给子Widget去处理,但调试后发现如果子Widget重新实现 ...
- Qt之键盘事件监听-实时响应大小写Capslock按键
目录 一.开篇 二.效果展示 三.实现思路 1.重写QLlinEdit 2.全局应用程序事件 3.windows钩子 四.相关文章 原文链接:Qt之键盘事件监听-实时响应大小写Capslock按键 一 ...
随机推荐
- javascript对象深拷贝,浅拷贝 ,支持数组
javascript对象深拷贝,浅拷贝 ,支持数组 经常看到讨论c#深拷贝,浅拷贝的博客,最近js写的比较多, 所以也来玩玩js的对象拷贝. 下面是维基百科对深浅拷贝的解释: 浅拷贝 One meth ...
- SQLAlchemy on the way
SQLAlchemy Trial This is a great ORM ( Object-Relational Mapper ) which is compatible with xxxx and ...
- Spring MVC 笔记 —— Spring MVC 文件上传
文件上传 配置MultipartResolver <bean id="multipartResolver" class="org.springframework.w ...
- mybatis 入门进阶之 pojo
有时候我们dao方法声明的入参需要是自定义的pojo,以满足复杂的查询条件. IWebUserCustomDao.java package com.mozi.dao; import java.util ...
- Swift3.0服务端开发(三) Mustache页面模板与日志记录
本篇博客主要介绍如果在Perfect工程中引入和使用Mustache页面模板与日志记录系统.Mustache页面模板类似于PHP中的smarty模板引擎或者Java中的JSTL标签.当然Mustach ...
- C++ 头文件系列(deque)
简介 deque是double ended queue(即双端队列)的简称. 就像C++中的大部分容器的一样,deque具有以下属性: 顺序的(sequence) 动态增长的(dynamic grow ...
- 初识JavaWEB
小荷才露尖尖角,早有蜻蜓立上头 首先,你需要知道你要学习哪些知识,掌握哪些技术?且听我娓娓道来. 第一阶段:HTML ,CSS ,JavaScript这三个是必须要掌握的,也是最基础的了.其实HEML ...
- Kubernetes(k8s)容器运行时(CRI)
Kubernetes节点的底层由一个叫做"容器运行时"的软件进行支撑,它负责比如启停容器这样的事情.最广为人知的容器运行时当属Docker,但它不是唯一的.事实上,容器运行时这个领 ...
- JMeter处理Cookie与Session
1.Cookie 添加方式:线程组-配置元件-HTTP Cookie 管理器,如下图: 2.Session 添加方式:线程组-前置处理器 -HTTP URL 重写修饰符,如下图: 有些tomcat的s ...
- 各种Python小玩意收集
快速排序 quicksort = lambda lst: [] if not lst else quicksort([i for i in lst[1:] if i <= lst[0]]) + ...