Qt源码剖析之事件循环系统
2025-3-8
| 2025-3-9
Words 1969Read Time 5 min
type
status
date
slug
summary
tags
category
icon
password

Qt源码剖析之事件循环系统

本文基于 Qt6.2 源码进行分析。从主事件循环入手,分析 Qt 事件循环系统的主要实现(部分细节省略)。

主事件循环

主事件循环负责整个 Qt 应用程序接收事件、分发事件的流程,运行在主线程。
写过 Qt 程序的都知道,一般新建一个 Qt 项目会默认生成一个 mian.cpp 文件,内容如下
其中 a.exec()会启动一个事件循环,即主事件循环。所谓的事件循环,简单来说就是在循环中处理事件。接下来我们从 QCoreApplication::exec()方法入手。

QCoreApplication::exec()

我们可以看到,主事件循环是通过调用 QEventLoop::exec(QEventLoop::ApplicationExec) 启动。

事件循环 QEventLoop::exec(ProcessEventsFlags flags)

从上述代码我们可以看到,事件循环是通过在while循环中不断调用processEvents()实现的。同时这里值得注意的是事件循环只在当前线程有效,事实上,在主事件循环的exec()的省略代码中,也有判断当前线程和事件循环所属线程是否一致的代码。
每个线程都可以有自己的事件循环。

QEventLoop::processEvents()

最终的事件循环和处理是在“事件调度器”—QAbstractEventDispatcherprocessEvents方法实现的。
不同的系统使用不同的事件调度器。其中类Unix系统使用的是 QEventDispatcherUNIX ,本文基于此类进行分析。

QEventDispatcherUNIX::processEvents()

该方法主要干两件事: 分发事件 和 监听事件

1. 事件分发 QCoreApplicationPrivate::sendPostedEvents(nullptr, 0, threadData)

事件调度器分发事件时,参数 receiver 传入的是 nullptr ,这意味着会分发 QCoreApplication 中的所有当前线程的事件队列 postEventList 中的事件,然后通过 QCoreApplication::sendEvent(r, e); 将事件发送到 notify() 中 。那么问题来了,这个事件队列中的事件是从哪里来的呢?
通过 Qt 的官方文档中,我们可以找到发送事件的两个相关方法 QCoreApplication::sendEvent()QCoreApplication::postEvent() 相信经常写 Qt 的多少都会用过这两个方法。我们看一下文档是怎么描述的。
> sendEvent() processes the event immediately. When it returns, the event filters and/or the object itself have already processed the event. For many event classes there is a function called isAccepted() that tells you whether the event was accepted or rejected by the last handler that was called.
> postEvent() posts the event on a queue for later dispatch. The next time Qt’s main event loop runs, it dispatches all posted events, with some optimization. For example, if there are several resize events, they are compressed into one. The same applies to paint events: QWidget::update() calls postEvent(), which eliminates flickering and increases speed by avoiding multiple repaints.
可以看到 postEvent() 会将事件投递到事件队列中,从源码上看也是如此。
事实上,我们从上面的 “1. 事件分发 QCoreApplicationPrivate::sendPostedEvents(nullptr, 0, threadData)” 开头的源码中可以看到,sendEvent() 是直接调用 notify() 方法直接响应事件的。

2. 监听事件

从源码上看,Qt 使用的是 Linux 的 poll 方法监听事件。需要监听的事件以 socket 的方式存储在事件调度器的 socketNotifiers 中,可以通过 QEventDispatcherUNIX::registerSocketNotifier 注册,这里就不多说了。
最后,是通过调用 qt_safe_poll 方法监听事件的,我们简单看一下源码。
通过底层的 poll 系统调用,线程等待事件发生,使当前线程陷入内核态,从而减少CPU资源的占用。
还有一个值得注意的地方,就是同时还监听了一个 QThreadPipe 管理的线程间通讯事件。这里简单分析以下。

QThreadPipe

初始化时创建一个无阻塞的事件文件描述符。
prepare() 创建一个监听该事件文件描述符的可读事件的 pollfd
wakeUp() 对事件文件描述符写入 1,此时可读事件发生。
可读事件发生时,读取事件值,并返回是否已经可读。
总结: 一个线程通过 prepare() 获取 可读pollfd 并监听。另一个线程通过 wakeUp() 写入事件值。这样第一线程就能被唤醒了。
最后“1. 事件分发 QCoreApplicationPrivate::sendPostedEvents(nullptr, 0, threadData)”还剩下最后一段处理 poll 返回值的代码。
poll返回值代表监听的文件描述符响应的数量,等于-1时为发生错误,等于0时代表无事发生超时而已
我们值分析大于0的情况。
事件发生后,将对应事件发送到 QCoreApplication::notify() 中。

后话

接触过 Qt 事件循环的一般都了解过 evnetFilter() 吧,还有就是重写event() 方法,那么这些方法是怎么被调用,以及它们的先后顺序是怎么样的呢? 我们可以从 QCoreApplication::notify() 入手
evnetFilter() 可分为 QApplication 和 一般 QObject。都需要调用 installEventFilter() 来启用。
notify() 最终调用 notify_helper()
这里顺序已经很明显了,sendThroughApplicationEventFilters -> sendThroughObjectEventFilters -> receiver->event
所以最终的调用顺序(或者说功能从强到弱的顺序): QCoreApplication::notify() -> QApplication::eventFilter -> Qt对象QObject::eventFilter -> 事件接收者的event()
QScopeGuard 源码分析enable_shared_from_this实现原理
Loading...