Qt事件循环与状态机事件循环的思考
写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习。首先我们都知道程序有一个主线程,在GUI程序中这个主线程也叫GUI线程,图形和绘图相关的函数都是由主线程来提供。主线程有个事件循环Event Loop,其实就是一个死循环在不断的等待你的消息队列,通过消息队列完成响应用户操作,绘图,以及相关操作。我们都知道QDialog有一个exec函数,这个函数会形成“模态”对话框,然后等待用户去输入OK还是Cancel,否则他绝不返回,如下
void test()
{
QDialog dialog;
dialog.exec(); qDebug() << "finish exec";
}
我们可以看这个简单的例子,可以看到,当dialog被exec之后,我们的qDebug是不会输出的,除非我们人为去点了对话框的OK,否则,就会一直卡在exec之上。这个时候可能同学会有我一开始一样的误解,我们会误以为此时事件循环停止了,其实并不是停止,而是阻塞住了。为什么会阻塞?因为test这个函数没有返回嘛!
m_stateManager->postEvent(ev);
投递事件官方API也说的很清楚,会立即返回,所以别去担心此时投递的事件进入不到消息队列,真正要关心的是此时dialog的exec让你的主线程阻塞了,这个时候消息队列上的事件都不会进行操作,都在等待dialog的返回,只有dialog返回,接下来的事件才会依次进行。要记住,消息是可以正常投递的。
那么,有没有办法可以让dialog.exec()立即返回,同时我的对话框还在呢?方法是有的
void test()
{
metaObject()->invokeMethod(this, "invokeTest", Qt::QueuedConnection); qDebug() << "finish exec";
} Q_INVOKABLE void invokeTest()
{
QDialog dialog;
dialog.exec();
}
答案就是用Qt提供的元对象系统Meta Object System的invokeMethod,并且将第三个参数设为Qt:QueuedConnection。从字面上我们也可以看的出来,这个调用不会去立即调用,相反他是异步的,他会把这个函数投递到事件队列里,也就是说,这个例子中的qDebug会输出,输出之后,事件队列才会去调用dialog.exec()这个函数,当然了,调用这个函数之后又会达到我们一开始的那个阻塞的效果,你通过异步到最后的触发,始终你需要去面对exec给你当前事件循环造成阻塞的问题。
让我们再深入一点,当一个事件循环的时候还算简单。但是我们知道,Qt中对于状态机他是有一个异步的事件循环的,也就是说外面有事件循环,状态机本身也有事件循环。比如
m_stateManager->postEvent(ev); m_tool->run();
你给状态机投递了一个事件,他根据状态迁移去调用你的tool,这一切看起来很美好,但如果你此时的tool是个跟之前一样的阻塞的exec呢?让我们来看一下。
void Tool::run()
{
QDialog dialog;
dialog.exec();
}
对于这个情况,当我们运行tool之后,我们的状态机就跟之前的主事件事件循环一样被阻塞了,也就是说如果我此时继续
m_stateManager->postEvent(ev2);
和之前一样,这个postEvent会立即返回,因为投递到事件队列都是立即返回的。但是关键的问题在于你的状态机整个事件循环都停止不动了,都在等你之前的tool运行结束,但因为你之前的tool是个dialog.exec()你必须手动去点OK,不然你的状态机事件循环就阻塞不动了,这个时候如果你的客户不断的去点你这个tool的event,那会产生噩梦般的效果----你点完OK之后又会来OK之后又会来OK。。。这其实就是你一旦点了OK,你的消息队列就又可以循环了,之前等待的ev就都会去执行了。而且要注意的就是,此时你的exec的执行在主线程上,只是不能进行返回,但还是可以接收诸如键盘,鼠标等事件投递。
前面也说了,事件循环和状态机循环是两个独立的循环,其实这也很好理解。如果没有事件循环,状态机事件怎么知道你有没有按下这个键?从而去投递给状态机呢?其实也就是说当你状态机事件阻塞的时候,你的主事件循环还在不断的接收你的键盘和鼠标的操作,这一点是没有影响的。
因此,要想实现在tool的时候我还能相应别的状态事件,其实做法也是一样的,就是
void Tool::run()
{
metaObject()->invokeMethod(this, "invokeTest", Qt::QueuedConnection);
} Q_INVOKABLE void invokeTest()
{
QDialog dialog;
dialog.exec();
}
立即返回,这个”立即返回“并不是说你的事情做完了,而是你更想让状态机能够进行之后事件的循环,别去因为你的dialog而耽误了大家。
最后说说模态这个主题,其实模态的理解就是你的消息队列都在正常进行,因为你不断的在等待,导致事件循环不能进行下去,必须你这边正常返回,你接下来的操作才能继续。
今天又重新思考了一下这个问题。同步的意思似乎就是必须要执行完成才能返回。异步的概念就是立即返回,之后执行,会把他扔到消息队列里,待同步函数处理完之后,然后去搜索事件队列进行操作。其实状态机归根结底就是一堆信号链接,只是他的方向是规矩状态迁移表来进行。作为主线程的Event Loop来说,当dialog进入exec的时候,就是就是在进行事件队列,并不是说他此时把事件队列给阻塞了,这个我之前理解有问题。exec的含义就是去处理事件队列,去处理事件循环,去检索当前还有哪些事件可以被处理,从而去正常处理。比如我们有一个主程序窗口MainWindow,有一个Dialog,此时我们去调用dialog的exec,内部会去创建一个QEventLoop,又因为这个dialog的所在线程和MainWindow在同一个线程上,所以看上去似乎是两个EventLoop,但实际上都是同一个线程的Event Loop(一个线程只能有一个Event Loop,这是原子性问题)。所以在dialog进行exec的时候他会去检索主线程上的事件队列去操作。
而我们之前讲的状态机,其实仔细想了想很简单,你就把他理解成是主程序总的Event Loop中的一个事件,他在进行操作的时候,不返回(tool去调用dialog的exec看上去似乎进行了事件循环在等待你新的event,但别忘了,你本身这个tool的run就是通过事件队列去触发的)所以必须要等待这个tool的exec返回,你的事件队列才能正常下去。
再次强调:
- exec并不是说事件队列被你阻塞,而是才是让你进入一个真正等待处理事件队列的过程。
- 同一个线程只能有一个Event Loop,这个可以参考CP单核心单线程的处理逻辑。
- 在进行事件队列进行事件操作的时候,其实内部就是同步的方式在进行,必须等待函数全部执行完毕才能真正返回才能真正进行之后的event,这也可以说的通我们之前举的状态机的例子,看上去这个状态机引发了我们的tool,tool中调用了dialog的exec,看上去似乎很美好,在等待状态事件了。但此时你这个exec不返回,你如何让事件Event Loop继续进行下去。
- 同步函数就是必须要等待函数操作完成之后才能返回的函数。异步函数就是直接返回,他具体什么时候进行操作,待具体实现查看。(也可能是本线程的事件队列,也可能是别的线程进行run)。如果是别的线程进行run的时候,你可能会去想这个立即返回的问题,其实很简单,Qt的run都是start,其实就跟postEvent一样,只是简单的把他注册给线程管理器,由线程管理器再去跑他的run函数,那你本地的start当然立即返回了。
Qt事件循环与状态机事件循环的思考的更多相关文章
- Qt事件机制浅析(定义,产生,异步事件循环,转发,与信号的区别。感觉QT事件与Delphi的事件一致,而信号则与Windows消息一致)
Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事 ...
- QT中的线程与事件循环理解(2)
1. Qt多线程与Qobject的关系 每一个 Qt 应用程序至少有一个事件循环,就是调用了QCoreApplication::exec()的那个事件循环.不过,QThread也可以开启事件循环.只不 ...
- QT中的线程与事件循环理解(1)
1.需要使用多线程管理的例子 一个进程可以有一个或更多线程同时运行.线程可以看做是“轻量级进程”,进程完全由操作系统管理,线程即可以由操作系统管理,也可以由应用程序管理.Qt 使用QThread 来管 ...
- JQuery在循环中绑定事件的问题详解
JQuery在循环中绑定事件的问题详解 有个页面上需要N个DOM,每个DOM里面的元素ID都要以数字结尾,比如说 ? 1 2 3 <input type="text" nam ...
- 不使用jquery情况下循环添加绑定事件方法
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- Angular4.x 创建组件|绑定数据|绑定属性|数据循环|条件判断|事件|表单处理|双向数据绑定
Angular4.x 创建组件|绑定数据|绑定属性|数据循环|条件判断|事件|表单处理|双向数据绑定 创建 angular 组件 https://github.com/angular/angular- ...
- for循环-鼠标移入事件
这里当鼠标移入触发事件时候,会报错,为什么呢?注意一下objs数组的长度是5. 因为在鼠标移入事件触发之前for循环已经将i变成5,objs[5]是不存在的,所以会报错. 可以用this来解决这种问题 ...
- js for循环中点击事件中无法获取每一个i值的问题
好像是第二次遇到这个问题,必须要总结一下!! <!DOCTYPE html> <html> <head> <meta charset="utf-8& ...
- QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数
QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...
随机推荐
- lfs遇到的一些问题--后续阶段
1.安装GPM-1.20.7,make install出错: prog/display-buttons.c:39:57: 致命错误:gpm.h:没有那个文件或目录 #include <gpm.h ...
- JavaScript高级程序设计22.pdf
操作节点 appendChild()用于向childNodes列表末尾添加一个节点,appendChild()返回新增的节点 var returnedNode=someNode.appendChild ...
- DIY常用网站
工作: 技术: 学习: 个人十佳博客介绍:http://hedengcheng.com/?p=676
- openstack liberty 版本按照官方文档手动整合 完成后 基于dashboard-horizon 创建虚拟机报错 用CL却是成功的 网络等验证都是正确的通过启动的虚拟实例测试以成功
- 遇到java.lang.OutOfMemoryError: Java heap space问题【持续跟踪中...】
昨天下午app服务端无缘无故又502错了,我打开tomcat 日志: INFO: org.tuckey.web.filters.urlrewrite.UrlRewriteFilter DEBUG: i ...
- 2014年到期的myeclipse5.5.1注冊码
假设点击Myeclipse的载入项目到server的图标没有反应,这就是MyEclipse过期了,下面是还能用一年的注冊码: subscriber: axin Serial:nLR8ZC-85557 ...
- 最严格的身份证校验(JavaScript版)
在JavaWeb研发过程中为了获取有效的用户信息,校验其数据的有效性非常是必要,以下贴出在项目中用到的关于身份证的校验: <!DOCTYPE HTML PUBLIC "-//W3C// ...
- C++访问sqlite3实践
Sqlite确实是一个比较好的本地数据库,从接触它的时候就喜欢上了它,它可以在很多情况下简化应用.不过以前都是在Java里面使用,或者Linux C下使用的,现在有个项目(C++)可能我会用到sqli ...
- (转载)OSI七层参考模型和TCP/IP四层参考模型
Mallory 网络模型概念浅析 网络模型一般是指OSI七层参考模型和TCP/IP四层参考模型. #只是一种设计==模型# Open System Interconnect的缩写,意为开放式系统互 ...
- NDK开发之JNIEnv参数详解
即使我们Java层的函数没有参数,原生方法还是自带了两个参数,其中第一个参数就是JNIEnv. 如下: native方法: public native String stringFromC(); pu ...