Qt事件和事件循环

在处理QT循环事件的时候遇到了问题,查了半天资料都没弄明白问题出在哪,后来找大牛同事问了一下,同事就给我写了QCoreApplication::processEvent()这个函数,好啦,终于搞定了,这里小记一下,以避免之后遇到。html

因而乎这里认真仔细的看了一下Qt的事件和事件循环。(引用了碎炎的博客)网络

 

事件和事件循环架构

 

做为一个事件驱动的工具包,事件和事件传递扮演者Qt架构中的中心角色。在本文中咱们不会给出一个对这个话题的全面的概述,咱们将着眼于一些线程相关的概念。异步

 

事件能被程序的内部和外部产生,举个例子:函数

QKeyEvent和QMouseEvent对象表明了一个键盘和鼠标的事件,它们从窗口由用户的操做而产生。工具

QTimerEvent对象是当某个时间被激发时投入,它们都由操做系统产生。oop

QChildEvent对象是当一个子窗口被添加或者移除时候被送入QObject的,它们的源头是Qt程序本身。ui

 

事件的重点是它们被产生的时候不会被传递;它们会先进入事件队列,某刻会被传递。传送者本身循环访问事件队列并把事件传递给目标QObject对象,所以称做事件循环。概念上说,时间循环就像这个:
spa

1.   while (is_active)操作系统

2.   {

3.       while (!event_queue_is_empty)

4.           dispatch_next_event();

5.    

6.       wait_for_more_events();

7.   }

 

咱们经过运行QCoreApplication::exec()来进入消息循环,这个循环直到exit()或者quit()被调用时才会被堵塞,而后退出。

 

这个”wait_for_more_events()”函数处于堵塞状态,直到有新的事件被产生了。假如咱们考虑它,全部在此刻可能产生的事件是外部源头的。所以,这个消息循环能被如下状况唤醒:

窗口管理活动(鼠标按键操做等);

套接字事件;

定时器事件;

其它线程中投递的事件、

 

在Unix-like系统中,窗口管理器经过套接字来通知应用程序,即便客户端使用它们来与x server通信。若是咱们决定用内部的套接字对去实现跨线程的事件投递,全部剩下的唤醒条件以下:

套接字;

定时器;

这个就是select(2)系统调用所作的;它监视着一系列的一系列的活动者的描述符,若是它们在必定的时间内没有特定的活动,它们就超时了。

 

一个运行着的事件循环须要什么?

这个不是完整的列表,可是若是你有总体画面,你将可以去猜想什么类须要一个运行着的事件循环。

 

Widgets的绘画和互动:QWidget::paintEvent()将在传递QPaintEvent对象时候被调用,这个对象将会在调用QWidget::update()或者窗口管理器的时候产生:响应的事件将须要一个时间循环去分发。

 

Timers:长话短说,它们在当select(2)或者超时的时候产生,所以它们须要让Qt在返回时间循环的时候做这些调用。

 

Networking“全部的底层Qt网络类 (QTcpSocket,QUdpSocket,QTcpServer等)都是异步设计的。当你调用ready(),它们只是返回已经可用的数据,当你调 用write(),它们只是将这个操做放入队列,适时会写入。只有当你返回消息循环的时候真实的读取,写入才会执行。注意它们确实提供了同步的方法,可是 它们的用法是不被提倡的,由于它们会堵塞事件循环。高级类,好比QNetworkAccessManager,简单的不提供同步API,须要一个事件循 环。

 

堵塞事件循环

 

在咱们讨论为何你应该从不堵塞消息循环以前,咱们试着分析堵塞的含义。假象你有一个按钮,它将会在它被点击的时候发出clicked信号;在咱们的对象中链接着一个槽函数,当你点击了那个按钮后,栈追踪将会像这样:

 

1.     main(int, char **)

2.     QApplication::exec()

3.     […]

4.     QWidget::event(QEvent *)

5.     Button::mousePressEvent(QMouseEvent *)

6.     Button::clicked()

7.     […]

8.     Worker::doWork()

在main函数中咱们启动了时间循环,日常的调用了 exex(),窗口管理器给咱们发送了一个鼠标点击事件,它被Qt内核取走,转换成QMouseEvent并被送往咱们widget的event()方 法,该方法被QApplication::notify()发送。由于按钮没有重写event(),基类方法将被调用,QWidget::event() 检测到了这个事件确实是一个鼠标点击事件,而后调用特定的事件处理函数,那就是Button::mousePressEvent(),咱们重写这个方法去 发送clicked()信号,这将会调用被链接的槽函数。

 

当该对象处理量很大,那么消息循环在做什么?咱们应该猜想它:什么都不作!它分发鼠标按下事件,而后就堵塞着等待着事件处理函数返回。这个就是堵塞了时间循环,它意味着没有消息被分发了,知道咱们从槽函数返回了,而后继续处理挂起的消息。

 

在消息循环被卡住的状况下,widgets将不能更新它们 自身,不可能有更多的互动,timers将不会被激发,网络通信将缓慢下来,或者中止。进一步的说,许多窗口管理器将检测到你的应用程序不在处理事件了, 而后告诉用户你的程序没有响应。这就是为何快速的对事件响应而且即时返回到事件循环是多么的重要!

 

强制事件分发

 

因此,假如咱们有一个很长的任务去运行可是又不但愿堵塞这 个消息循环,该怎么作呢?一个可能的回答是将这个任务移到另外一个线程中,在下一个张洁咱们将看到这是如何作的。咱们也能手动强制事件循环去运行,这个方法 是经过在堵塞的任务函数中调用QCoreApplication::processEvent()来实现 的,QCoreApplication::processEvent()将处理全部在消息队列中的消息并返回给调用者。

 

另外一个可选的选项是咱们可以强制重入事件循环的对象,就是QEventLoop类。经过调用QEventLoop::exec()咱们将重入事件循环,而后咱们能将槽函数QVentLoop::quit()链接到信号上去使它退出。举个例子:

 

1.   QNetworkAccessManager qnam;

2.   QNetworkReply *reply = qnam.get(QNetworkRequest(QUrl(...)));

3.   QEventLoop loop;

4.   QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));

5.   loop.exec();

6.   /* reply has finished, use it */

 

 

QNetworkReply不提供堵塞的API,它要求一个在运行的事件循环。咱们进入了一个本地的事件循环,而后当回复完成时候,这个本地的循环退出了。

 

要特别当心的是在其它路径下重入事件循环:它可能致使不但愿的递归!让咱们回到前面看看按钮的例子。假如咱们在槽函数中调用了QCoreApplication::processEvent(),当用户点击了这个按钮,这个槽函数将被再次调用:

1.     main(int, char **)

2.     QApplication::exec()

3.     […]

4.     QWidget::event(QEvent *)

5.     Button::mousePressEvent(QMouseEvent *)

6.     Button::clicked()

7.     […]

8.     Worker::doWork() // first, inner invocation

9.     QCoreApplication::processEvents() // we manually dispatch events and…

10.   […]

11.   QWidget::event(QEvent * ) // another mouse click is sent to the Button…

12.   Button::mousePressEvent(QMouseEvent *)

13.   Button::clicked() // which emits clicked() again…

14.   […]

15.   Worker::doWork() // DANG! we’ve recursed into our slot.

 

一个快速并简便的变通方法是把QEventLoop::ExcludeUserInputEvent传递给QCoreApplication::processEvents(),这会告诉消息循环不要再次分发任何用户的输入事件。

 

幸运的是,这个相同的事情不会在检测事件中发生。事实上,它们被Qt经过特殊的方法处理了,只有当运行的时间循环有了一个比deleteLater被调用后更小的”nesting”值才会被处理:

 

1.   QObject *object = new QObject;

2.   object->deleteLater();

3.   QDialog dialog;

4.   dialog.exec();

 

将不会使object成为一个悬空指针。相同的东西被应用 到了本地的事件循环中。惟一的一个显著区别我已经发现了,它在假如当没有事件循环在运行的时候deleteLater被调用了的条件下,而后第一个消息循 环进入了后会取走这个事件,而后删除这个object。这是至关合理的,由于Qt不知道任何外部的循环将最终影响这个检测,所以立刻删除了这个 object。

相关文章
相关标签/搜索