写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习。首先我们都知道程序有一个主线程,在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返回,你的事件队列才能正常下去。

再次强调:

  1. exec并不是说事件队列被你阻塞,而是才是让你进入一个真正等待处理事件队列的过程。
  2. 同一个线程只能有一个Event Loop,这个可以参考CP单核心单线程的处理逻辑。
  3. 在进行事件队列进行事件操作的时候,其实内部就是同步的方式在进行,必须等待函数全部执行完毕才能真正返回才能真正进行之后的event,这也可以说的通我们之前举的状态机的例子,看上去这个状态机引发了我们的tool,tool中调用了dialog的exec,看上去似乎很美好,在等待状态事件了。但此时你这个exec不返回,你如何让事件Event Loop继续进行下去。
  4. 同步函数就是必须要等待函数操作完成之后才能返回的函数。异步函数就是直接返回,他具体什么时候进行操作,待具体实现查看。(也可能是本线程的事件队列,也可能是别的线程进行run)。如果是别的线程进行run的时候,你可能会去想这个立即返回的问题,其实很简单,Qt的run都是start,其实就跟postEvent一样,只是简单的把他注册给线程管理器,由线程管理器再去跑他的run函数,那你本地的start当然立即返回了。

Qt事件循环与状态机事件循环的思考的更多相关文章

  1. Qt事件机制浅析(定义,产生,异步事件循环,转发,与信号的区别。感觉QT事件与Delphi的事件一致,而信号则与Windows消息一致)

    Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事 ...

  2. QT中的线程与事件循环理解(2)

    1. Qt多线程与Qobject的关系 每一个 Qt 应用程序至少有一个事件循环,就是调用了QCoreApplication::exec()的那个事件循环.不过,QThread也可以开启事件循环.只不 ...

  3. QT中的线程与事件循环理解(1)

    1.需要使用多线程管理的例子 一个进程可以有一个或更多线程同时运行.线程可以看做是“轻量级进程”,进程完全由操作系统管理,线程即可以由操作系统管理,也可以由应用程序管理.Qt 使用QThread 来管 ...

  4. JQuery在循环中绑定事件的问题详解

    JQuery在循环中绑定事件的问题详解 有个页面上需要N个DOM,每个DOM里面的元素ID都要以数字结尾,比如说 ? 1 2 3 <input type="text" nam ...

  5. 不使用jquery情况下循环添加绑定事件方法

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  6. Angular4.x 创建组件|绑定数据|绑定属性|数据循环|条件判断|事件|表单处理|双向数据绑定

    Angular4.x 创建组件|绑定数据|绑定属性|数据循环|条件判断|事件|表单处理|双向数据绑定 创建 angular 组件 https://github.com/angular/angular- ...

  7. for循环-鼠标移入事件

    这里当鼠标移入触发事件时候,会报错,为什么呢?注意一下objs数组的长度是5. 因为在鼠标移入事件触发之前for循环已经将i变成5,objs[5]是不存在的,所以会报错. 可以用this来解决这种问题 ...

  8. js for循环中点击事件中无法获取每一个i值的问题

    好像是第二次遇到这个问题,必须要总结一下!! <!DOCTYPE html> <html> <head> <meta charset="utf-8& ...

  9. QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数

    QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...

随机推荐

  1. Android TextView : “Do not concatenate text displayed with setText”

    参考:http://stackoverflow.com/questions/33164886/android-textview-do-not-concatenate-text-displayed-wi ...

  2. 【转】谁说Vim不是IDE?(三)

    谁说Vim不是IDE?(三)   常用插件 之所以说Vim形成了自己的生态环境,就是因为Vim具备开放的插件体系,开发者为了提升开发效率,为Vim编写了数以万计的插件,我们可以根据需要任意选择,也可以 ...

  3. 【转】linux下安装opencv

    Installation in Linux These steps have been tested for Ubuntu 10.04 but should work with other distr ...

  4. iOS UITextField 输入字数限制的实现

    首先你的ViewController需要实现 UITextFieldDelegate 代理, 其次,需要字数限制的UITextField实例的代理要设置成 self(ViewController) 然 ...

  5. Spring boot 内存优化

    转自:https://dzone.com/articles/spring-boot-memory-performance It has sometimes been suggested that Sp ...

  6. 转 python 之 分割参数getopt

    python 之 分割参数getopt os下有个方法walk,非常的好用,用来生成一个generator.每次可以得到一个三元tupple,其中第一个为起始路径,第二个为起始路径下的文件夹,第三个是 ...

  7. 屌丝程序猿赚钱之道 之APP

    假设你已经通过APP赚到了钱,那么本文对你而言没有意义.倒是希望你可以给我们诸多建议. 通过制作APP或者说手机应用赚钱,相信是非常多程序猿希望做的事情.也确实有一些人通过APP赚到了钱. 对于程序猿 ...

  8. 【iOS-Android开发对照】 之 APP入口

    [iOS-Android开发对照]之 APP入口 [图片 Android vs iOS] 提纲 对照分析iOS,Android的入口, iOS,Android的界面单元 为什么要有那样的生命周期 继承 ...

  9. POJ 3614 Sunscreen 优先队列 贪心

    题意 有C个奶牛去晒太阳 (1 <=C <= 2500),每个奶牛各自能够忍受的阳光强度有一个最小值和一个最大值,太大就晒伤了,太小奶牛没感觉. 而刚开始的阳光的强度非常大,奶牛都承受不住 ...

  10. HDU 1242 rescue and 优先级队列的条目

    Problem Description Angel was caught by the MOLIGPY! He was put in prison by Moligpy. The prison is ...