Qt源码阅读(四) 事件循环
事件系统
文章为本人理解,如有理解不到位之处,烦请各位指正。
Qt的事件循环是所有Qt开发者都无法避免的一个重要概念。因此,本篇博客将介绍Qt源码中与事件循环相关的部分,帮助读者更好地理解Qt事件循环的机制。
在深入源码之前,先抛出几个问题。随后,我们将通过源码,逐一解析,揭开事件循环的面纱。
- 事件循环是什么?
- 事件是怎么产生的?
- 事件是如何处理的?
什么是事件循环?
在ChatGPT💬上搜索到的概念是:
在Qt中,事件循环是一种机制,用于处理各种异步事件。事件循环通过一个事件队列来管理和调度事件,当队列中有事件时,事件循环会从队列中依次取出事件并处理,直到队列为空或者事件循环被中断。
事件的产生可以是用户输入、系统信号、网络请求、定时器等,Qt提供了一系列的事件处理函数和信号槽机制,可以方便地将这些事件与具体的操作相绑定。
因此,Qt的事件循环机制是Qt应用程序实现异步响应和多线程编程的基础。
个人理解,Qt的事件循环是通过一个队列来循环处理事件的机制。当队列中有事件时,事件循环会处理事件;如果队列中没有事件,则事件循环会阻塞等待。
事件是如何产生的?
事件的产生可以分为两种:
-
程序外部产生:指系统产生的事件,例如鼠标按下(MouseButtonPress)、按键按下(KeyPress)等。Qt通过捕捉系统事件,将其封装成自己的QEvent类,再将事件发送出去。
-
程序内部产生:指在代码中手动创建一个事件,然后通过
sendEvent
/postEvent
将事件发送到事件循环中。其中,sendEvent
是阻塞型的发送方式,会等待事件处理完成后再继续执行;而postEvent
是非阻塞型的发送方式,会将事件放入事件队列中,并立即返回。
事件是如何处理的?
让我们通过一个流程图简单了解事件从发出到处理的过程。
在接下来的解析中,我们将通过分析源代码,逐步验证流程图中的每个步骤。请各位读者耐心继续往下阅读
下面我们将通过分析源码,来揭秘为什么sendEvent
/postEvent
是阻塞和非阻塞的、以及事件的处理流程。
sendEvent
首先,我们看sendEvent
:
bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{
// sendEvent是阻塞调用
Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());
if (event)
event->spont = false;
return notifyInternal2(receiver, event);
}
可以看到,sendEvent
是调用了notifyInternal2
这个函数
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
...
// Qt enforces the rule that events can only be sent to objects in
// the current thread, so receiver->d_func()->threadData is
// equivalent to QThreadData::current(), just without the function
// call overhead.
// 事件只能在同一个线程被send
QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData;
QScopedScopeLevelCounter scopeLevelCounter(threadData);
if (!selfRequired)
return doNotify(receiver, event);
return self->notify(receiver, event);
}
进一步跟踪到其doNotify
函数
static bool doNotify(QObject *receiver, QEvent *event)
{
if (receiver == nullptr) { // serious error
qWarning("QCoreApplication::notify: Unexpected null receiver");
return true;
}
#ifndef QT_NO_DEBUG
// 检查接受线程与当前是否同线程
QCoreApplicationPrivate::checkReceiverThread(receiver);
#endif
// QWidget类必须用QApplication
return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}
然后就到了咱们这次分析的第一个重点——事件通知:QCoreApplicationPrivate::notify_helper
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
// ...
}
通过分析这个函数,我们就可以大致知道事件处理的几个流程:
- 判断QCoreApplication有没有安装事件过滤器,有就把信号发送到QCoreApplication所安装事件过滤器里,由事件过滤器对事件进行处理。
// send to all application event filters (only does anything in the main thread)
if (QCoreApplication::self
&& receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()
&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
filtered = true;
return filtered;
}
- 如果QCoreApplication没有安装事件过滤器或者所安装的事件过滤器不处理,则判断事件接受对象有没有安装事件过滤器,有就将事件发送到事件过滤器去处理。
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, event)) {
filtered = true;
return filtered;
}
事件过滤器的具体分析,在后面会分析,现在我们先关注主要的流程。
- 同样,如果事件过滤器不进行处理,则直接调用事件接受对象的
event
函数进行处理。因为是直接调用的对象的event
,所以sendEvent
函数是阻塞的。
// deliver the event
// 直接调用对象的event函数,所以是阻塞的
consumed = receiver->event(event);
return consumed;
postEvent
在下面的分析过程中,我们将通过对postEvent
函数的分析,来解释为什么这个函数是非阻塞的。
让我们对postEvent
函数进行分析,以便解密其工作原理。
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
// ...
}
- 首先判断事件接收对象是否为空
// 事件的接收者不能为空
if (receiver == nullptr) {
qWarning("QCoreApplication::postEvent: Unexpected null receiver");
delete event;
return;
}
- 将事件接收对象所在线程的post事件列表上锁,如果已经被锁了,就把事件删除掉,并返回,防止泄露。
// 对事件接受对象所在线程的事件处理列表上锁
auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
if (!locker.threadData) {
// posting during destruction? just delete the event to prevent a leak
delete event;
return;
}
- 将一些可以压缩的事件进行压缩,及多个事件压缩成只推送最后的一个事件。Qt界面的
update
就是这个操作,为了防止多次刷新导致卡顿,短时间内多次的调用update
可能只会刷新一次
// if this is one of the compressible events, do compression
// 将重复的事件,进行压缩
if (receiver->d_func()->postedEvents
&& self && self->compressEvent(event, receiver, &data->postEventList)) {
Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
return;
}
- 将事件插入接收对象所在线程的post事件列表中,并唤醒线程的事件调度器,来进行事件的处理。所以
postEvent
是非阻塞的,因为其只是把事件插入了线程的事件列表,唤醒事件调度器之后便返回。
// ...
// 将事件加入事件队列
data->postEventList.addEvent(QPostEvent(receiver, event, priority));
eventDeleter.take();
event->posted = true;
++receiver->d_func()->postedEvents;
data->canWait = false;
locker.unlock();
// 唤醒事件分发器
QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
if (dispatcher)
dispatcher->wakeUp();
事件循环是怎么遍历的?
在上面的内容中,我们只是通过跟随sendEvent
来了解了事件处理的流程,但是postEvent
将事件插入事件队列中,事件循环又是如何去循环运转的呢?
让我们从常见的return a.exec()
开始,深入源码,详细了解事件循环的运行机制吧😶🌫️!
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
上面是一个经典的QtGUI程序的main函数,调用a.exec()
int QCoreApplication::exec()
{
...
threadData->quitNow = false;
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
int returnCode = eventLoop.exec();
...
}
而看QApplication::exec
的源码,实际上就是开启了一个事件循环(QEventLoop
)。同样,我们去看QEventLoop::exec
的源码,进一步了解处理事件队列的步骤是什么。
int QEventLoop::exec(ProcessEventsFlags flags)
{
// ...
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCode.loadRelaxed();
}
上面可以看到,QEvenLoop::exec
里,是一个while
循环,循环的去调用processEvent
,而且设置了WaitForMoreEvents
就是说,如果没有事件,就阻塞等待。
void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int ms)
{
QThreadData *data = QThreadData::current();
if (!data->hasEventDispatcher())
return;
QElapsedTimer start;
start.start();
while (data->eventDispatcher.loadRelaxed()->processEvents(flags & ~QEventLoop::WaitForMoreEvents)) {
if (start.elapsed() > ms)
break;
}
}
阅读processEvent
,其调用了线程的事件调度器QAbstrctEventDispatcher
,而这个类是一个抽象基类,根据不同的平台,有不同的实现,我们以windows下(QEventDispatcherWin32
)的为例,接着分析事件处理的流程。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
...
// To prevent livelocks, send posted events once per iteration.
// QCoreApplication::sendPostedEvents() takes care about recursions.
sendPostedEvents();
...
}
void QEventDispatcherWin32::sendPostedEvents()
{
Q_D(QEventDispatcherWin32);
if (d->sendPostedEventsTimerId != 0)
KillTimer(d->internalHwnd, d->sendPostedEventsTimerId);
d->sendPostedEventsTimerId = 0;
// Allow posting WM_QT_SENDPOSTEDEVENTS message.
d->wakeUps.storeRelaxed(0);
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData.loadRelaxed());
}
可以看到,事件调度器兜兜转转最终还是调用了QCoreApplication
的sendPostEvents
void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
QThreadData *data)
{
// ...
if (receiver && receiver->d_func()->threadData != data) {
qWarning("QCoreApplication::sendPostedEvents: Cannot send "
"posted events for objects in another thread");
return;
}
...
while (i < data->postEventList.size()) {
...
// first, we diddle the event so that we can deliver
// it, and that no one will try to touch it later.
pe.event->posted = false;
QEvent *e = pe.event;
QObject * r = pe.receiver;
--r->d_func()->postedEvents;
Q_ASSERT(r->d_func()->postedEvents >= 0);
// next, update the data structure so that we're ready
// for the next event.
const_cast<QPostEvent &>(pe).event = nullptr;
locker.unlock();
const auto relocker = qScopeGuard([&locker] { locker.lock(); });
QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)
// after all that work, it's time to deliver the event.
QCoreApplication::sendEvent(r, e);
// careful when adding anything below this point - the
// sendEvent() call might invalidate any invariants this
// function depends on.
}
cleanup.exceptionCaught = false;
}
我们一个一个的分块分析:
- 判断是否在一个线程
if (receiver && receiver->d_func()->threadData != data) {
qWarning("QCoreApplication::sendPostedEvents: Cannot send "
"posted events for objects in another thread");
return;
}
- 将事件发送出去(
sendEvent
)
while (i < data->postEventList.size()) {
...
// first, we diddle the event so that we can deliver
// it, and that no one will try to touch it later.
pe.event->posted = false;
QEvent *e = pe.event;
QObject * r = pe.receiver;
--r->d_func()->postedEvents;
Q_ASSERT(r->d_func()->postedEvents >= 0);
// next, update the data structure so that we're ready
// for the next event.
const_cast<QPostEvent &>(pe).event = nullptr;
locker.unlock();
const auto relocker = qScopeGuard([&locker] { locker.lock(); });
QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)
// after all that work, it's time to deliver the event.
QCoreApplication::sendEvent(r, e);
// careful when adding anything below this point - the
// sendEvent() call might invalidate any invariants this
// function depends on.
}
可以看到,核心还是调用sendEvent
将事件发送出去,而前面我们对sendEvent
的源码分析我们可以看到,事件先是经过事件过滤器,再经过对象的event函数,来进行事件的处理。
所以,下面我们将介绍:事件过滤器
事件过滤器
在实际应用中,我们经常要将某一个窗口部件的某个事件如鼠标滑轮滚动拦截,然后执行我们自己想要的操作。这个时候,我们就可以用到**事件过滤器(EventFilter
) **
首先,我们需要自己编写一个eventFilter
函数,
bool Class::eventFilter(QObject* watcher, QEvent* event)
{
//以过滤鼠标滚轮事件为例
if (object == m_watcherObject && event->type() == QEvent::Wheel) {
// do something
return true;
}
QWidget::eventFilter(watcher, event);
}
然后,我们需要为要拦截的某个窗口部件,安装事件过滤器
void Class::initUI()
{
QWidget* m_watcherObject = new QWidget(this);
// 为对象安装一个事件过滤器
m_watcherObject->installEventFilterr(this);
}
initUI();
么一个对象安装的多个事件过滤器,会以什么样的顺序触发呢?我们在帮助文档中可以看到:
后安装的事件过滤器会先触发
这一点,我们可以在源码里得到佐证:
void QObject::installEventFilter(QObject *obj)
{
Q_D(QObject);
if (!obj)
return;
if (d->threadData != obj->d_func()->threadData) {
qWarning("QObject::installEventFilter(): Cannot filter events for objects in a different thread.");
return;
}
if (!d->extraData)
d->extraData = new QObjectPrivate::ExtraData;
// clean up unused items in the list
d->extraData->eventFilters.removeAll((QObject*)nullptr);
d->extraData->eventFilters.removeAll(obj);
d->extraData->eventFilters.prepend(obj);
}
可以清楚的看到,事件过滤器,是以prepend
的形式被添加进事件过滤器列表的。
在QCoreApplicationPrivate::sendThroughObjectEventFilters
中,则是遍历事件过滤器列表。
if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) {
for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) {
QObject *obj = receiver->d_func()->extraData->eventFilters.at(i);
if (!obj)
continue;
if (obj->d_func()->threadData != receiver->d_func()->threadData) {
qWarning("QCoreApplication: Object event filter cannot be in a different thread.");
continue;
}
if (obj->eventFilter(receiver, event))
return true;
}
}
return false;
那么,当有鼠标滚轮事件触发的时候,我们可以看到sendEvent
会先走到事件过滤器里,如果eventFilter
返回一个true,那么事件就不会被继续派发,否则,将会将事件发送到其他的事件过滤器里进行处理,如果其他的事件过滤器均对该事件不进行处理,那么事件将会继续往下派发,走到事件的处理函数event
夹带私货时间
- 之前有说到
processEvent
,添加一个小经验。当我们有时候不得不在主线程循环执行很耗时的操作的时候,这个时候,界面就会刷新不过来,就会导致界面卡顿,影响使用。但是,我们可以在这个循环里,手动调用qApp->processEvent()
,这样就可以手动调用处理掉所有的事件,就可以解决卡顿的问题。
创作不易,如果对您有帮助,点赞、关注、收藏支持一下!不甚感激!😊