• 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(event)->level = loopLevel + scopeLevel;
        }
    
        // delete the event on exceptions to protect against memory leaks till the event is
        // properly owned in the postEventList
        QScopedPointer 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 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 (!event_type && !receiver && data->postEventList.startOffset >= 0) {
                    const QPostEventList::iterator it = data->postEventList.begin();
                    data->postEventList.erase(it, it + data->postEventList.startOffset);
                    data->postEventList.insertionOffset -= data->postEventList.startOffset;
                    Q_ASSERT(data->postEventList.insertionOffset >= 0);
                    data->postEventList.startOffset = 0;
                }
            }
        };
        CleanUp cleanup(receiver, event_type, data);
    
        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(pe).event = nullptr;
    
            locker.unlock();
            const auto relocker = qScopeGuard([&locker] { locker.lock(); });
    
            QScopedPointer 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;
    }
    

    我们一个一个的分块分析:

    1. 判断是否在一个线程

      if (receiver && receiver->d_func()->threadData != data) {
          qWarning("QCoreApplication::sendPostedEvents: Cannot send "
                   "posted events for objects in another thread");
          return;
      }
      
    2. 一个有意思的异常安全的处理,不需要try/catch块

      // 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 (!event_type && !receiver && data->postEventList.startOffset >= 0) {
                  const QPostEventList::iterator it = data->postEventList.begin();
                  data->postEventList.erase(it, it + data->postEventList.startOffset);
                  data->postEventList.insertionOffset -= data->postEventList.startOffset;
                  Q_ASSERT(data->postEventList.insertionOffset >= 0);
                  data->postEventList.startOffset = 0;
              }
          }
      };
      CleanUp cleanup(receiver, event_type, data);
      

    定义了一个结构体CleanUp,结构体的析构函数(~CleanUp)保存了函数退出时需要执行的清理操作。然后在栈上创建了一个结构体对象,遍历事件列表时,异常退出,那么就会调用自动调用~CleanUp的析构函数。

    1. 将事件发送出去(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(pe).event = nullptr;
      
              locker.unlock();
              const auto relocker = qScopeGuard([&locker] { locker.lock(); });
      
              QScopedPointer 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的形式被添加进事件过滤器列表的。
    那么,当有鼠标滚轮事件触发的时候,我们可以看到sendEvent会优先走到事件过滤器里,如果eventFilter返回一个true,那么事件就不会被继续派发,否则,将会将事件发送到其他的事件过滤器里进行处理,如果其他的事件过滤器均对该事件不进行处理,那么事件将会继续往下派发,走到事件的处理函数event

    event

    接下来,就到了事件处理的最后一站,event函数,这个函数比较简单,我们可以自己重写这个函数,对事件进行自定义的处理。

    bool Class::event(QEvent *e)
    {
        switch (e->type()) {
        case QEvent::Whell:
            // do something
            return true;
    
        default:
            if (e->type() >= QEvent::User) {
                customEvent(e);
                break;
            }
            return false;
        }
        return true;
    }
    

    夹带私货时间

    1. 之前有说到processEvent,添加一个小经验。当我们有时候不得不在主线程循环执行很耗时的操作的时候,这个时候,界面就会刷新不过来,就会导致界面卡顿,影响使用。但是,我们可以在这个循环里,手动调用qApp->processEvent(),这样就可以手动调用处理掉所有的事件,就可以解决卡顿的问题
  • 相关阅读:
    windows 不能ping通虚拟机问题
    论文写作心得
    [附源码]计算机毕业设计JAVAjsp大学生学业预警系统
    Python4-检查用户主目录中是否存在目录,如果不存在则创建目录
    vue+echarts项目十二:使用webSocket优化项目:合并图表到一个页面并添加 切换主题和切换全屏功能
    Windows编译Chrome浏览器【多图警告^-^】
    Linux基础 常见问题-模拟输入事件工具xdotool
    整数因数分解方法
    笔记---fastadmin踩坑之旅,fastadmin thinkphp 编辑数据下拉菜单 默认选择之前的值
    lottie 动画在 vue 中的使用
  • 原文地址:https://www.cnblogs.com/codegb/p/17274163.html