乱谈Qt事件循环嵌套
- 本文旨在说明:QDialog::exec()、QMenu::exec()等开启的局部事件循环,易用的背后,还有很多的陷阱...
引子
Qt 是事件驱动的,基本上,每一个Qt程序我们都会通过QCoreApplication或其派生类的exec()函数来开启事件循环(QEventLoop):
int main(int argc, char**argv)
{
QApplication a(argc, argv);
return a.exec();
}
但是在同一个线程内,我们可以开启多个事件循环,比如通过:
- QDialog::exec()
- QDrag::exec()
- QMenu::exec()
- ...
这些东西都很常用,不是么?它们每一个里面都在执行这样的语句:
QEventLoop loop; //事件循环
loop.exec();
既然是同一线程内,这些显然是无法并行运行的,那么只能是嵌套运行。
如何演示?
如何用最小的例子来直观说明这个问题呢?
利用定时器来演示应该是最方便的。于是,很容易写出来这样的代码:
#include <QtCore> class Object : public QObject
{
public:
Object() {startTimer(200); } protected:
void timerEvent(QTimerEvent *) {
static int level = 0;
qDebug()<<"Enter: <<"++level;
QEventLoop loop; //事件循环
loop.exec();
qDebug()<<"Leave: "<<level;
}
}; int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
Object w;
return a.exec();
}
然后我们可以期待看到:
Enter: 1
Enter: 2
Enter: 3
...
但是,很让人失望,这并不会工作。因为Qt对Timer事件派发时进行了处理:
- 如果当前在处理Timer事件,新的Timer将不会被派发。
演示
我们对这个例子进行一点改进:
- 收到Timer事件后,我们立即post一个自定义事件(然后我们在对自定义事件的相应中开启局部的事件循环)。这样Timer事件的相应可以立即返回,新的Timer事件可以持续被派发。
另外,为了友好一点,使用了 QPlainTextEdit 来显示结果:
#include <QtGui>
#include <QtCore> class Widget : public QPlainTextEdit
{
public:
Widget() {startTimer(200); } protected:
bool event(QEvent * evt)
{
if (evt->type() == QEvent::Timer) {
qApp->postEvent(this, new QEvent(QEvent::User));
} else if (evt->type() == QEvent::User) {
static int level = 0;
level++;
this->appendPlainText(QString("Enter : %1").arg(++level));
QEventLoop loop;
loop.exec();
this->appendPlainText(QString("Leave: %1").arg(level));
}
return QPlainTextEdit::event(evt);
}
}; int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
有什么用?
这个例子确实没有什么,因为似乎没人会写这样的代码。
但是,当你调用
- QDialog::exec()
- QMenu::exec()
- QDrag::exec()
- ...
等函数时,实际上就启动了嵌套的事件循环,而如果不小心的话,还有遇到各种怪异的问题!
== QDialog::exec() vs QDialog::open()== 在 QDialog 模态对话框与事件循环 以及 漫谈QWidget及其派生类(四) 我们解释过QDialog::exec()。它最终就是调用QEventLoop::exec()。
QDialog::exec()这个东西是这么常用,以至于我们很少考虑这个东西的不利因素。QDialog::open()尽管被官方所推荐,但是似乎很少有人用,很多人可能还不知道它的存在。
但是Qt官方blog中:
一文介绍了 exec() 可能造成的危害,并鼓励大家使用 QDialog::open()
在Qt官方的Qt Quarterly中: * QtQuarterly30 之 New Ways of Using Dialogs 对QDialog::open()有详细的介绍
QDialog::open()劣势与优势
看个例子:我们通过颜色对话框选择一个颜色,
- 使用静态函数,写法很简介(内部调用exec()启动事件循环)
void Widget::onXXXClicked()
{
QColor c = QColorDialog::getColor();
}
- 对话框的常见用法,使用exec()启动事件循环,很直观
void Widget::onXXXClicked()
{
QColorDialog dlg(this);
dlg.exec();
QColor c = dlg.currentColor();
}
- 使用open()函数,比较不直观(因为是异步,需要链接一个槽)
void Widget::onXXXClicked()
{
QColorDialog *dialog = new QColorDialog;
dialog->open(this, SLOT(dialogClosed(QColor)));
}
void Widget::dialogClosed(const QColor &color)
{
QColor = color;
}
好处嘛(就摘录Andreas Aardal Hanssen的话吧):
- By using open() instead of exec(), you need to write a few more lines of code (implementing the target slot). But what you gain is very significant: complete control over execution. Now, the event loop is no longer nested/reentered, you’re not blocking inside a magic exec() function
局部事件循环导致崩溃
Kde开发者官方blog中描述这个问题:
在某个槽函数中,我们通过QDialog::exec() 弹出一个对话框。
void ParentWidget::slotDoSomething()
{
SomeDialog dlg( this ); //分配在栈上的对话框
if (dlg.exec() == QDialog::Accepted ) {
const QString str = dlg.someUserInput();
//do something with with str
}
}
如果这时ParentWidget或者通过其他方式(比如dbus)得到通知,需要被关闭。会怎么样?
程序将崩溃:ParentWidget析构时,将会delete这个对话框,而这个对话框却在栈上。
简单模拟一下(在上面代码中加一句即可):
void ParentWidget::slotDoSomething()
{
QTimer::singleShot(1000, this, SLOT(deleteLater()));
...
这篇blog最终给出的结论是:将对话框分配到堆上,并使用QPointer来保存对话框指针。
上面的代码,大概要写成这样:
void ParentWidget::slotDoSomething()
{
QWeakPointer<SomeDialog> dlg = new SomeDialog(this);
if (dlg.data()->exec() == QDialog::Accepted ) {
const QString str = dlg.data()->someUserInput();
//do something with with str
} else if(!dlg) {
//....
}
if (!dlg) {
delete dlg.data();
}
}
感兴趣的可以去看看原文。比较起来 QDialog::open() 应该更值得考虑。
QCoreApplication::sendPostedEvents()
当程序做繁重的操作时,而又不愿意开启一个新线程时,我们都会选择调用
QCoreApplication::sendPostedEvents ()
来使得程序保持相应。这和前面提到的哪些exec()开启局部事件循环的效果其实是完全一样的。
无论是这个,还是QEventLoop::exec()最终都是调用:
QAbstractEventDispatcher::processEvents()
来进行事件派发。前面的问题也都是由它派发的事件引起的。
乱谈Qt事件循环嵌套的更多相关文章
- QT虚拟小键盘设计--qt事件循环,事件发送的理解
有人讲到QT5.7及其以后的版本才自带免费的小键盘插件. QT5.10中关于QKeyEvent类:点击打开链接 QT sendEvent和PostEvent, 点击打开链接 my god,我今天安装了 ...
- Qt事件循环与状态机事件循环的思考
写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习.首先我们都知道程序有一个主线程,在GUI程序中这个主线程也叫GUI线程,图形和绘图相关的函数都是由主线程来提供.主线程有个事件循环 ...
- 【转】Qt事件循环与线程 二
转自:http://blog.csdn.net/changsheng230/article/details/6153449 续上文:http://blog.csdn.net/changsheng230 ...
- QEventLoop以及QT事件循环
1.一般我们的事件循环都是由exec()来开启的,例如下面的例子: 1 QCoreApplicaton::exec() 2 QApplication::exec() 3 QDialog::exec() ...
- 浅谈Qt事件的路由机制:鼠标事件
请注意,本文是探讨文章而不是教程,是根据实验和分析得出的结果,可能是错的,因此欢迎别人来探讨和纠正. 这几天对于Qt的事件较为好奇,平时并不怎么常用,一般都是用信号,对于事件的处理,一般都是需要响应键 ...
- Qt事件机制浅析(定义,产生,异步事件循环,转发,与信号的区别。感觉QT事件与Delphi的事件一致,而信号则与Windows消息一致)
Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事 ...
- Qt 的线程与事件循环——可打印threadid进行观察槽函数到底是在哪个线程里执行,学习moveToThread的使用)
周末天冷,索性把电脑抱到床上上网,这几天看了 dbzhang800 博客关于 Qt 事件循环的几篇 Blog,发现自己对 Qt 的事件循环有不少误解.从来只看到现象,这次借 dbzhang800 的博 ...
- 【Qt开发】事件循环与线程 一
事件循环与线程 一 初次读到这篇文章,译者感觉如沐春风,深刻体会到原文作者是花了很大功夫来写这篇文章的,文章深入浅出,相信仔细读完原文或下面译文的读者一定会有收获. 由于原文很长,原文作者的行文思路是 ...
- QT事件
qtevents多线程工作object存储 Another Look at Events(再谈Events) 最近在学习Qt事件处理的时候发现一篇很不错的文章,是2004年季刊的一篇文章,网上有这篇文 ...
随机推荐
- 自己封装的Socket组件,实现服务端多进程共享Socket对象,协同处理客户端请求
DotNet.Net.MySocket是SLB.NET(Server Load Balance服务器负载均衡)项目中的核心组件. 在实际的项目中发现,单进程的服务端处理高并发的客户请求能力有限. 所以 ...
- UIActivityViewController 自定义选项
UIActivityViewController 自定义选项 重写 UIActivity 类 建议下载github上源码学习一下 https://github.com/samvermette/SVWe ...
- 第三百四十五天 how can I 坚持
最烦这个阶段了,飘忽不定,或许这种感觉未来会很值得回味. 我为什么会那么烦,是因为错过而悔恨,还是..其实我还是很在乎的,好想一切都随缘. 让我讲struts.springmvc,可是什么都不会. 我 ...
- 自定义元素 – 在 HTML 中定义新元素
本文翻译自 Custom Elements: defining new elements in HTML,在保证技术要点表达准确的前提下,行文风格有少量改编和瞎搞. 原译文地址 本文目录 引言 用时髦 ...
- MecAnim
[MecAnim] MecAnim是Unity 4.0推出的新的动画系统,新系统使用Animator组件来控制动画,老系统使用Animation组件来控制动画.此篇讲述MecAnim系统. What ...
- MSSQL手札四 MSSQL的函数
和oracle一样,sql也可以自己定义函数 一个返回值,引用DEMO如下: 编写一个函数,该函数,可以通过输入借书时间来判断是否到期,当借阅时间大于30天,返回已经过期:否则返回还未到期. CREA ...
- JS制作的简单的三级及联
前台: <form id="form1" runat="server"> <div> 省 <select id="Pro ...
- HDU 5534 Partial Tree (完全背包变形)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5534 题意: 给你度为1 ~ n - 1节点的权值,让你构造一棵树,使其权值和最大. 思路: 一棵树上 ...
- HDU3466Proud Merchants(贪心&背包)
http://acm.hdu.edu.cn/showproblem.php?pid=3466 题目大意是说n个物品每个物品的花费是p,但是如果你现在的钱少于q就买不了这个物品,每个物品的价值是v,求有 ...
- HDU1150Machine Schedule(二分图最大匹配的DFS解法)
题目大意就是说有两台机器,分别有n,m种模式可以调节,有k个工作,某一个工作i可以在第一台机器的a[i]模式下或第二台机器的b[i]模式下工作,两台机器的初始模式为0,问如何分配这K件工作使得两台机器 ...