最近一直在倒腾事件循环的东西,通过查看Qt源码多少还是有点心得体会,在这里记录下和大家分享。总之,对于QStateMachine状态机本身来说,需要有QEventLoop::exec()的驱动才能支持,也就是说,在你Qt程序打开的时候,最后一句

QCoreApplication::exec()

已经由内部进入了状态循环

int QCoreApplication::exec()
{
...QThreadData *threadData = self->d_func()->threadData;if (threadData != QThreadData::current()) {qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());return -1;}if (!threadData->eventLoops.isEmpty()) {qWarning("QCoreApplication::exec: The event loop is already running");return -1;}
QEventLoop eventLoop;self->d_func()->in_exec = true;self->d_func()->aboutToQuitEmitted = false;int returnCode = eventLoop.exec();
...
}

由上面我们可以得到以下几个结论:

  1. 很自然而然的我们可以看到,事件队列只跟线程有关,即同一个线程,如论你如何更改,最终你的事件循环和事件队列本身都是属于这个线程的。
  2. QApplication::exec()这种都会去最终调用QEventLoop::exec()形成事件循环。

其实不仅仅是QApplication,我们知道QDialog有类似的exec()函数,其实内部也会进入一个局部的事件循环:

int QDialog::exec()
{
...QEventLoop eventLoop;d->eventLoop = &eventLoop;QPointer<QDialog> guard = this;(void) eventLoop.exec(QEventLoop::DialogExec);if (guard.isNull())return QDialog::Rejected;d->eventLoop = 0;
...
}

可以看到,QDialog的这种exec()其实内部也是最终产生了一个栈上的QEventLoop来进行事件循环。这个时候,肯定有同学会有如下疑问:

  • 那如果我在QApplication::exec()中调用了QDialog的exec(),那QEventLoop如何来分配指责?

其实答案在上面已经有了,对于一个线程来说,其所拥有的事件队列是唯一的,但其所拥有的事件循环可以是多个,但绝对是嵌套关系,并且是只有当前QEventLoop被激活。我们可以看QEventLoop的exec()内部究竟在做什么。

int QEventLoop::exec(ProcessEventsFlags flags)
{Q_D(QEventLoop);
...#if defined(QT_NO_EXCEPTIONS)while (!d->exit)processEvents(flags | WaitForMoreEvents | EventLoopExec);
#elsetry {while (!d->exit)processEvents(flags | WaitForMoreEvents | EventLoopExec);} catch (...) {...} 

可以看到其内部正是在通过一个while循环去不断的processEvents(),我们再来看processEvents():

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{Q_D(QEventLoop);if (!d->threadData->eventDispatcher)return false;if (flags & DeferredDeletion)QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);return d->threadData->eventDispatcher->processEvents(flags);
}

可以很明显的看到,对于一个线程来说,无论其事件循环是内层嵌套还是在外层,其最终都会去调用

d->threadData->eventDispatcher

这个是线程唯一的,从而也证明了我们上面的结论,事件队列对于线程来说是一对一的。那么如何来验证我们另一个观点,即在同一个线程上事件循环可以是多个,并且是嵌套关系,当前只有一个激活呢?我们写一个小的Demo来验证一下:

MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::on_pushButton_clicked()
{QDialog dialog;dialog.exec();
}

很简单,我们在MainWindow上放一个button,他的点击函数会出现一个dialog并且进入局部事件循环,之后我们在QEventLoop::exec()下断点,分别查看在没打开Dialog之前和打开之后调用栈的区别:

0    QEventLoop::processEvents    qeventloop.cpp    144    0xb717dfc3
1    QEventLoop::exec    qeventloop.cpp    204    0xb717e1cf
2    QCoreApplication::exec    qcoreapplication.cpp    1225    0xb7181098
3    QApplication::exec    qapplication.cpp    3823    0xb74c7eaa
4    main    main.cpp    10    0x804a4ce    

这是没打开Dialog之前,可以看到此时的事件循环正是QCoreApplication内部提供的QEventLoop。当我们打开Dialog之后再来查看

0    QEventLoop::processEvents    qeventloop.cpp    144    0xb717dfc3
1    QEventLoop::exec    qeventloop.cpp    204    0xb717e1cf
2    QDialog::exec    qdialog.cpp    562    0xb7a949c4
...
27    QEventDispatcherGlib::processEvents    qeventdispatcher_glib.cpp    425    0xb71b7cc6
28    QGuiEventDispatcherGlib::processEvents    qguieventdispatcher_glib.cpp    204    0xb7595140
29    QEventLoop::processEvents    qeventloop.cpp    149    0xb717e061
30    QEventLoop::exec    qeventloop.cpp    204    0xb717e1cf
31    QCoreApplication::exec    qcoreapplication.cpp    1225    0xb7181098
32    QApplication::exec    qapplication.cpp    3823    0xb74c7eaa
33    main    main.cpp    10    0x804a4ce    

可以看到此时的事件循环正是QDialog的exec(),其实也很好理解,内部的exec()不退出,自然就不能运行外部的exec(),但千万别以为此时就事件阻塞了,很多人跟我一样,一开始总以为QDialog::exec()就会造成事件阻塞,其实事件循环依旧在不断处理,唯一的区别就是这时的事件循环是在QDialog上。

理解了基本的事件循环和事件队列之后,让我们再来看一下QStateMachine与事件循环的关联:

首先我们来看一下QStateMachine自己的postEvent()

void QStateMachine::postEvent(QEvent *event, EventPriority priority)
{
...switch (priority) {case NormalPriority:d->postExternalEvent(event);break;case HighPriority:d->postInternalEvent(event);break;}d->processEvents(QStateMachinePrivate::QueuedProcessing);
}

可以看到,他其实内部自己维护了两个队列,一个是普通优先级的externalEventQueue,一个是高优先级的internalEventQueue。由此我们也可以得出Qt官方文档所说的状态机的事件循环和队列跟我们上文提的事件队列和事件循环压根就是两码事,千万别搞混了。可以看到他内部也会进行processEvents(),我们来看一下:

void QStateMachinePrivate::processEvents(EventProcessingMode processingMode)
{Q_Q(QStateMachine);if ((state != Running) || processing || processingScheduled)return;switch (processingMode) {case DirectProcessing:if (QThread::currentThread() == q->thread()) {_q_process();break;} // fallthrough -- processing must be done in the machine threadcase QueuedProcessing:processingScheduled = true;QMetaObject::invokeMethod(q, "_q_process", Qt::QueuedConnection);break;}
}

很显然,状态机的实现逻辑就是把_q_process()这个异步调用,放到事件队列中去,这也印证了官方文档所说的

Note that this means that it executes asynchronously, and that it will not progress without a running event loop.

这句话,也就是说状态机的运转就是向当前线程的事件队列丢一个_q_process(),然后等待事件循环给他进行调用,所以接下来问题的关键就是_qt_process()

void QStateMachinePrivate::_q_process()
{...    Q_Q(QStateMachine);Q_ASSERT(state == Running);Q_ASSERT(!processing);processing = true;processingScheduled = false;while (processing) {if (stop) {processing = false;break;}QSet<QAbstractTransition*> enabledTransitions;QEvent *e = new QEvent(QEvent::None);enabledTransitions = selectTransitions(e);if (enabledTransitions.isEmpty()) {delete e;e = 0;}
...enabledTransitions = selectTransitions(e);if (enabledTransitions.isEmpty()) {delete e;e = 0;}}
if (!enabledTransitions.isEmpty()) {q->beginMicrostep(e);microstep(e, enabledTransitions.toList());q->endMicrostep(e);}#endifif (stop) {stop = false;stopProcessingReason = Stopped;...
}

可以看到,状态机的process本身就是一个大循环,flag为processing(这也是避免多次投递_q_process()的标记位),进入此函数后状态机会去根据状态迁移表去调用相应的函数。这里面其实也有可以扩展的地方,就是当我的状态机本身去调用的函数是一个不返回的,也就是说比如QDialog::exec(),进入了事件循环,那我此时的状态机会卡在

microstep(e, enabledTransitions.toList());

这个函数上,我们也知道exec()函数可以让我们正常进行事件派发,所以当事件队列又去调用状态机事件的时候,因为上文processing这个flag的存在,我们在

void QStateMachinePrivate::processEvents(EventProcessingMode processingMode)
{Q_Q(QStateMachine);if ((state != Running) || processing || processingScheduled)return;
...
}

会立即返回,所以你也不需要去担心状态机的阻塞以及效率问题,因为此时他只做队列的post维护,但processEvents()压根不能执行。

这个问题还有一个有意思的地方是需要注意的,就拿我们之前的语境,状态机本身调用的函数会去调用一个QDialog::exec(),那么在创建好dialog之后,我的事件循环就在这个dialog中的QEventLoop开始做了,所以有一点需要注意就是我的_q_process()

void QStateMachinePrivate::_q_process()
{Q_Q(QStateMachine);Q_ASSERT(state == Running);Q_ASSERT(!processing);processing = true;processingScheduled = false;
#ifdef QSTATEMACHINE_DEBUGqDebug() << q << ": starting the event processing loop";
#endifwhile (processing) {if (stop) {processing = false;break;}
...
}

因为while循环的存在,所以我的队列可能此时有3个事件,A,B,C,其中我执行A的时候我创建了个Dialog,此时我的所有事件循环都建立在这个新创建的dialog的内部的那个QEventLoop,那么当我关闭这个Dialog的时候,我while继续执行,但此时我所在的事件循环已经是QCoreApplication的exec内部的QEventLoop了,这点需要特别注意。

还有一个需要注意的是倘若你想让状态机在执行耗时函数的时候可以立即返回或者像上文一样出现Dialog,此时状态机不能继续循环,但你的需要是想让状态机可以继续正常运行处理别的事件的时候,你就需要在状态机处理事件的内部调用

bool QMetaObject::invokeMethod();

这个函数,通过第三个参数选择Qt::QueuedConnection你可以很轻松的把这个dialog投递当QEventLoop的事件队列中,而让当前状态机正常返回,然后QEventLoop的processEvents()会去处理这个dialog,并创建之后调用exec()形成局部事件循环。

总体来说,需要记住以下几点:

  • 事件队列对于线程来说是一对一的,而事件循环对于线程来说是多对一的,但他们是嵌套关系,并且只有当前QEventLoop被激活。
  • 状态机的驱动需要通过现存的事件循环来推动,并且其内部维护的事件队列和QEventLoop的事件队列是两回事。
  • 当状态机的_q_process()没有返回的时候,Qt不会再去派发_q_process事件。并且总会在_q_process循环中针对当前的所有状态机事件进行逐步处理。

转载于:https://www.cnblogs.com/rickyk/p/4203680.html

深入理解QStateMachine与QEventLoop事件循环的联系与区别相关推荐

  1. php event loop,理解javascript中的事件循环(Event Loop)

    背景 在研究js的异步的实现方式的时候,发现了JavaScript 中的 macrotask 和 microtask 的概念.在查阅了一番资料之后,对其中的执行机制有所了解,下面整理出来,希望可以帮助 ...

  2. QEventLoop事件循环的使用

    一.描述 QEventLoop提供一种进入和离开事件循环的方法,在任何时候,都可以创建一个QEventLoop实例,然后调用exect()来启动事件循环,在循环期间,可以调用exit()来退出循环强制 ...

  3. Qt对话框的事件循环实例分析

    写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习.首先我们都知道程序有一个主线程,在GUI程序中这个主线程也叫GUI线程,图形和绘图相关的函数都是由主线程来提供.主线程有个事件循环 ...

  4. Qt对话框的事件循环分析(子线程中不能创建UI窗体分析)

    重要: GUI线程和辅助线程 如前所述,每个程序在启动时都有一个线程.这个线程被称为"主线程"(在Qt应用程序中也称为"GUI线程").Qt GUI必须在这个线 ...

  5. Qt对话框的事件循环分析(子线程中不能创建UI窗体分析2)

    Qt事件机制 QT-UI 后端 重要: GUI线程和辅助线程 如前所述,每个程序在启动时都有一个线程.这个线程被称为"主线程"(在Qt应用程序中也称为"GUI线程&quo ...

  6. 面试率 90% 的JS事件循环Event Loop,看这篇就够了!! !

    面试率 90% 的JS事件循环Event Loop,看这篇就够了!! ! 事件循环(Event Loop)大家应该并不陌生,它是前端极其重要的基础知识.在平时的讨论或者面试中也是一个非常高频的话题. ...

  7. 浏览器与node事件循环

    我们都知道在浏览器中由于dom操作,js是单线程的. 为了利用多核CPU的计算能力,HTML5提出Web Worker标准,允许JavaScript脚本创建多个线程,但是子线程完全受主线程控制,且不得 ...

  8. JavaScript事件循环剖析

    前言 相信对于刚学习JavaScript的新手来说,去理解JS中的事件循环原理以及异步执行过程比较困难,但是这是JS必须要会的基础知识,逃避不能解决问题,笔者曾经也被这个知识点困扰过,现根据以往的经验 ...

  9. [转] JavaScript:彻底理解同步、异步和事件循环(Event Loop)

    一. 单线程 我们常说"JavaScript是单线程的". 所谓单线程,是指在JS引擎中负责解释和执行JavaScript代码的线程只有一个.不妨叫它主线程. 但是实际上还存在其他 ...

最新文章

  1. Direct2D介绍
  2. 数据结构 判断单链表是否有环 C
  3. linux下使用python_Python随笔(一)、Linux系统下python环境的安装
  4. 火山引擎视频云:从toC到toB,如何将最好的技术开放出去
  5. 1-1、article元素
  6. python绘制条形图例题_python matplotlib库绘制条形图练习题
  7. ON1 photo raw 2021(ps/lr滤镜插件) 下载及安装
  8. pytorch —— nn网络层 - 卷积层
  9. java 遍历 likedlist_Java集合02----LinkedList的遍历方式及应用
  10. 深度探索QT窗口系统——几何篇
  11. wordpress页面里可不可以写php,WordPress开发中如何在html中包含php
  12. 【Python】利用tkinter开发AI对战井字棋游戏
  13. 面试官常问 webpack 面试题
  14. 张馨予一幅画拍出几十万,然而范冰冰却一直在向粉丝要画!
  15. 名利如过往云烟,知足就好
  16. From Mother Teresa
  17. 迅猛快捷——基于Gtid搭建Mysql主从,gtid实现主从切换自动同步——@$23$人鱼的眼泪
  18. iOS 摸鱼周报 #53 | 远程办公正在成为趋势
  19. Coursera | Andrew Ng (01-week-3-3.8)—激活函数的导数
  20. GO函数内部程序执行顺序

热门文章

  1. 你没看错!TCL品牌日10万台洗衣机免费送
  2. ios播放器相关(音乐列表获取)
  3. STM32学习笔记V1.1GPIO寄存器的ODR、BSRR、BRR
  4. 什么是知识管理?知识管理的方法?
  5. 企业邮箱签名设置攻略,注册邮箱后一定要做的事
  6. C语言_求1到某个数之间的所有素数
  7. 数学公式公式获取工具 Mathpix snipping Tool
  8. 微信小程序开发实战 ②①(网路请求Promise化)
  9. 有5个人坐在一起,问第5个人多少岁?他说比第4个人大2岁
  10. 什么是混合移动App开发?