首页 > 代码库 > Qt事件循环与状态机事件循环的思考
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而耽误了大家。
最后说说模态这个主题,其实模态的理解就是你的消息队列都在正常进行,因为你不断的在等待,导致事件循环不能进行下去,必须你这边正常返回,你接下来的操作才能继续。
Qt事件循环与状态机事件循环的思考