Qt源码阅读 事件循环

Posted 师从名剑山

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Qt源码阅读 事件循环相关的知识,希望对你有一定的参考价值。

事件系统

文章为本人理解,如有理解不到位之处,烦请各位指正。

文章目录


Qt的事件循环,应该是所有Qter都避不开的一个点,所以,这篇博客,咱们来了解源码中一些关于Qt中事件循环的部分。
先抛出几个疑问,根据源代码,下面一一进行解析。

  1. 事件循环是什么?
  2. 事件是怎么产生的?
  3. 事件是如何处理的?

什么是事件循环?

对于Qt事件循环个人理解是,事件循环是一个队列去循环处理事件。当队列中有事件时,则去处理事件,如果没有事件时,则会阻塞等待。

事件是如何产生的?

事件的产生可以分为两种:

  1. 程序外部产生
  2. 程序内部产生

程序外部所产生的事件主要是指系统产生的事件,比如说鼠标按下(MouseButtonPress)、按键按下(KeyPress)等,Qt捕捉系统的事件,然后将系统事件封装成自己的QEvent类,再将事件发送出去。

程序内部产生的事件主要指我们在代码里,手动创建一个事件,然后将事件通过sendEvent/postEvent,来发送到事件循环中。而sendEventpostEvent区别又在于一个是阻塞的(sendEvent)一个是非阻塞的(postEvent)。

我们结合源码分析,看一下sendEventpostEvent分别干了什么导致一个是阻塞的一个是非阻塞的。

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)

    // Note: when adjusting the tracepoints in here
    // consider adjusting QApplicationPrivate::notify_helper too.
    Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());
    bool consumed = false;
    bool filtered = false;
    Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);

    // 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;
    
    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event)) 
        filtered = true;
        return filtered;
    

    // deliver the event
    // 直接调用对象的event函数,所以是阻塞的
    consumed = receiver->event(event);
    return consumed;

然后我们可以看到主要有几个流程:

  1. 判断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;
    
    
  2. 判断事件接受对象,有没有安装事件过滤器,有就将信号发送到事件过滤器。

    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event)) 
        filtered = true;
        return filtered;
    
    

    具体遍历事件接受对象所安装的事件过滤器的代码如下:

    bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event)
    
        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;
    
    

    我们可以看到,只要事件被一个事件过滤器所成功处理,那么后续的事件过滤器就不会被响应。同时,参看Qt帮助手册中有提及到:

    If multiple event filters are installed on a single object, the filter that was installed last is activated first.

    后插入的事件过滤器会被优先响应。 具体安装事件过滤器,我们在后面进行分析。

  3. 直接调用事件接受对象的event函数进行处理。因为是直接调用的对象的event,所以说,sendEvent函数会阻塞等待。

        // deliver the event
        // 直接调用对象的event函数,所以是阻塞的
        consumed = receiver->event(event);
        return consumed
    

postEvent

完整代码如下:

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)

    Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());

	// 事件的接收者不能为空
    if (receiver == nullptr) 
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    

	// 对事件接受对象所在线程的事件处理列表上锁
    auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
    if (!locker.threadData) 
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    

    QThreadData *data = locker.threadData;

    // 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;
    

    if (event->type() == QEvent::DeferredDelete)
        receiver->d_ptr->deleteLaterCalled = true;

    if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) 
        // remember the current running eventloop for DeferredDelete
        // events posted in the receiver's thread.

        // Events sent by non-Qt event handlers (such as glib) may not
        // have the scopeLevel set correctly. The scope level makes sure that
        // code like this:
        //     foo->deleteLater();
        //     qApp->processEvents(); // without passing QEvent::DeferredDelete
        // will not cause "foo" to be deleted before returning to the event loop.

        // If the scope level is 0 while loopLevel != 0, we are called from a
        // non-conformant code path, and our best guess is that the scope level
        // should be 1. (Loop level 0 is special: it means that no event loops
        // are running.)
        int loopLevel = data->loopLevel;
        int scopeLevel = data->scopeLevel;
        if (scopeLevel == 0 && loopLevel != 0)
            scopeLevel = 1;
        static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
    

    // delete the event on exceptions to protect against memory leaks till the event is
    // properly owned in the postEventList
    QScopedPointer<QEvent> eventDeleter(event);
    Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
    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();

  1. 判断事件接收对象是否为空

    // 事件的接收者不能为空
    if (receiver == nullptr) 
        qWarning("QCoreApplication::postEvent: Unexpected null receiver");
        delete event;
        return;
    
    
  2. 将事件接收对象所在线程的post事件列表上锁,如果已经被锁了,就把事件删除掉,并返回,防止泄露。

    // 对事件接受对象所在线程的事件处理列表上锁
    auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);
    if (!locker.threadData) 
        // posting during destruction? just delete the event to prevent a leak
        delete event;
        return;
    
    
  3. 将一些可以压缩的事件进行压缩,及多个事件压缩成只推送最后的一个事件。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;
    
    
  4. 将事件插入接收对象所在线程的post事件列表中,并唤醒线程的事件调度器,来进行事件的处理。所以postEvent是非阻塞的,因为其只是把事件插入了线程的事件列表,唤醒事件调度器之后便返回

        // delete the event on exceptions to protect against memory leaks till the event is
        // properly owned in the postEventList
        QScopedPointer<QEvent> eventDeleter(event);
        Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
        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();
    

事件是如何处理的?

在Qt中,事件的接收者都是QObject,而QObject中事件处理是调用event函数。如果当时对象不处理某个事件,就会将其转发到父类的event进行处理。
而事件的处理,主要分为三个部分:

  1. 先是由事件循环遍历事件
  2. 然后判断事件接受对象有没有安装事件过滤器(installEventFilter),有安装的话,就把事件丢给事件过滤器(eventFilter)进行处理。
  3. 如果没有安装事件过滤器或者事件过滤器对该事件不进行处理的话,那么,事件将会进一步转发到event函数里进行处理。

所以,在这一章节,我们同样一步一步的分析这三个点。

事件循环是怎么遍历的?

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)

    // ### Qt 6: consider splitting this method into a public and a private
    //           one, so that a user-invoked processEvents can be detected
    //           and handled properly.
    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());

可以看到,事件调度器最终还是调用了QCoreApplicationsendPostEvents

void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,
                                               QThreadData *data)

    if (event_type == -1) 
        // we were called by an obsolete event dispatcher.
        event_type = 0;
    

    if (receiver && receiver->d_func()->threadData != data) 
        qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                 "posted events for objects in another thread");
        return;
    

    ...

    // Exception-safe cleaning up without the need for a try/catch block
    struct CleanUp 
        QObject *receiver;
        int event_type;
        QThreadData *data;
        bool exceptionCaught;

        inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :
            receiver(receiver), event_type(event_type), data(data), exceptionCaught(true)
        
        inline ~CleanUp()
        
            if (exceptionCaught) 
                // since we were interrupted, we need another pass to make sure we clean everything up
                data->canWait = false;
            

            --data->postEventList.recursion;
            if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())
                data->eventDispatcher.loadRelaxed()->wakeUp();

            // clear the global list, i.e. remove everything that was
            // delivered.
            if (

Qt文档阅读笔记-void QObject::deleteLater()解析

[slot] void QObject::deleteLater()

这个对象的进行计划删除。

当对这个对象的操作结束回到事件循环时,这个对象将会被删除。

如果这个函数不在事件循环中调用,一旦事件循环被启动,这个对象就会被删除。

如果deleteLater()是在主事件循环后面调用的,该对象不会被删除。

在多线程中调用要注意了:从Qt4.8后,如果deleteLater()在线程中调用,并且这个线程无事件循环,那么这个对象将会在线程销毁时被析构。

注意:使用deleteLater(),对象要想被成功析构,这个控制(函数、操作)一定要回到事件循环。

这个函数是线程安全的。

 

以上是关于Qt源码阅读 事件循环的主要内容,如果未能解决你的问题,请参考以下文章

Qt文档阅读笔记-void QObject::deleteLater()解析

Qt文档阅读笔记-void QObject::deleteLater()解析

QT事件循环

Qt源码阅读 moveToThread

QT中的线程与事件循环理解

Qt 的线程与事件循环