乱谈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事件循环嵌套,布布扣,bubuko.com

时间: 2024-10-03 14:01:49

乱谈Qt事件循环嵌套的相关文章

Qt事件循环与状态机事件循环的思考

写下这个给自己备忘,关于事件循环以及多线程方面的东西我还需要多多学习.首先我们都知道程序有一个主线程,在GUI程序中这个主线程也叫GUI线程,图形和绘图相关的函数都是由主线程来提供.主线程有个事件循环Event Loop,其实就是一个死循环在不断的等待你的消息队列,通过消息队列完成响应用户操作,绘图,以及相关操作.我们都知道QDialog有一个exec函数,这个函数会形成“模态”对话框,然后等待用户去输入OK还是Cancel,否则他绝不返回,如下 void test() { QDialog di

浅谈Qt事件的路由机制:鼠标事件

请注意,本文是探讨文章而不是教程,是根据实验和分析得出的结果,可能是错的,因此欢迎别人来探讨和纠正. 这几天对于Qt的事件较为好奇,平时并不怎么常用,一般都是用信号,对于事件的处理,一般都是需要响应键盘按键事件的时候,也用得毫无问题,因此也没怎么注意过,翻了下一般qt的教材<精通Qt4编程(第二版)>,里面12.1是这么说的. 当用户按下一个鼠标键时,这个事件首先被发给当前拥有焦点的窗口部件. 看到这里,我第一反应是,真的是这样吗,我表示十分地好奇,于是就赶忙试验了一下.代码比较简单,没有注释

Qt:事件和事件循环

最近想要了解一下Qt线程,但在对相关资料师都是从线程和事件循环开始将.对于事件循环是个相对很抽象的概念,研究了很久也很难在脑子里建立起一个具体的模型.今天在这里对这几天研究内容做总结,为以后做参考. 一.事件 网上很多资料都将的很清楚.在这里重点是明确一下一个事件发出后对于该事件的一个调用顺序.在这里我们在QApplication安装一个过滤器,在一个一个Qwidget安装一个过滤器 代码如下: class Label : public QWidget { public: Label() { i

Qt 的线程与事件循环——可打印threadid进行观察槽函数到底是在哪个线程里执行,学习moveToThread的使用)

周末天冷,索性把电脑抱到床上上网,这几天看了 dbzhang800 博客关于 Qt 事件循环的几篇 Blog,发现自己对 Qt 的事件循环有不少误解.从来只看到现象,这次借 dbzhang800 的博客,就代码论事,因此了解到一些 Qt 深层的实现,虽然是在 Qt 庞大的构架里只算的是冰山的一角,确让人颇为收益. 从 dbzhang800 的博客中转载两篇关于事件循环的文章,放在一起,写作备忘. 再次提到的一点是:事件循环和线程没有必然关系. QThread 的 run() 方法始终是在一个单独

Qt事件机制概览

Qt事件机制概览 Qt事件机制概览 消息循环 Qt事件循环 简介 QEventLoop 跨线程的信号和槽与事件循环 模态窗口 Native widget or Alien widget 创建Native widget 创建QApplication的message-only窗口 派发事件的公共基础方法 source code QApplication的创建过程 QWidget native QWidget 的创建过程 普通native widget回调过程 QApplication的message

QT事件

qtevents多线程工作object存储 Another Look at Events(再谈Events) 最近在学习Qt事件处理的时候发现一篇很不错的文章,是2004年季刊的一篇文章,网上有这篇文章的翻译版,但是感觉部分地方翻译的比较粗糙,不是很明确.索性重新翻译了一遍,并引用了原翻译版的一段译注.以下都是用自己能理解的方式来翻译的,由于水平有限,有很多不足的地方,希望大家指正. Another Look at Events (再谈Events) by Jasmin Blanchette 什

[GUI] QT事件与X11的关系

做了一段时间linux下与QT事件相关的工作,经常会遇到X11,总是苦于无法完全理解其与linux以及QT事件之间的关系,所以用两篇文章来简单总结下linux中的图形管理和QT事件与X11的关系. <1> linux中的图形管理 <2> QT事件与X11的关系 1. Qt中的事件 参考自<C++ GUI Qt 4编程>中第7章给出的Qt事件定义及说明: Qt的事件(event)是由窗口系统或者Qt自身产生,用以响应所发生的各类事情.当用户按下或者松开键盘或者鼠标上的按键

事件循环和线程没有必然关系(就像Windows子线程默认没有消息循环一样),模态对话框和事件循环也没有必然关系(QWidget直接就可以)

周末天冷,索性把电脑抱到床上上网,这几天看了 dbzhang800 博客关于 Qt 事件循环的几篇 Blog,发现自己对 Qt 的事件循环有不少误解.从来只看到现象,这次借 dbzhang800 的博客,就代码论事,因此了解到一些 Qt 深层的实现,虽然是在 Qt 庞大的构架里只算的是冰山的一角,确让人颇为收益. 从 dbzhang800 的博客中转载两篇关于事件循环的文章,放在一起,写作备忘. 再次提到的一点是:事件循环和线程没有必然关系. QThread 的 run() 方法始终是在一个单独

Qt事件机制浅析

Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事件: 按键按下和松开. 鼠标事件: 鼠标移动,鼠标按键的按下和松开. 拖放事件: 用鼠标进行拖放. 滚轮事件: 鼠标滚轮滚动. 绘屏事件: 重绘屏幕的某些部分. 定时事件: 定时器到时. 焦点事件: 键盘焦点移动. 进入和离开事件: 鼠标移入widget之内,或是移出. 移动事件: widget的位