event system
The article is my own understanding, if there is something wrong with my understanding, please correct me.
Article directory
Qt's event loop should be a point that all Qters cannot avoid. Therefore, in this blog, let's understand some parts of the source code about the event loop in Qt.
First throw a few questions, according to the source code, the following one by one analysis.
What is an event loop?
My personal understanding of the Qt event loop is that the event loop is a queue to cycle through events. When there is an event in the queue, it will process the event, and if there is no event, it will block and wait.
How are events generated?
Event generation can be divided into two types:
- Generated outside the program
- internally generated
Events generated outside the program mainly refer to events generated by the system, such as mouse button press (MouseButtonPress), key press (KeyPress), etc. Qt captures system events, then encapsulates system events into its own class, and then encapsulates the QEvent
event send it out.
The events generated inside the program mainly mean that we manually create an event in the code, and then send the event to the event loop through sendEvent
/ . postEvent
And the difference is that one is blocking ( ) and the sendEvent
other is non-blocking ( ).postEvent
sendEvent
postEvent
Let's combine the source code analysis to see what we did sendEvent
and postEvent
what caused one to be blocked and the other to be non-blocked.
sendEvent
The complete source code is as follows:
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);
}
As you can see, this function sendEvent
is callednotifyInternal2
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);
}
further traced to its doNotify
function
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);
}
then toQCoreApplicationPrivate::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;
}
Then we can see that there are several main processes:
-
Determine whether QCoreApplication has an event filter installed, and if so, send the signal to the event filter, and the event filter will process the event.
// 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; }
-
Determine whether the event accepting object is installed or not, and if so, send the signal to the event filter.
// send to all receiver event filters if (sendThroughObjectEventFilters(receiver, event)) { filtered = true; return filtered; }
The specific code for traversing the event filter installed by the event accepting object is as follows:
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; }
We can see that as long as an event is successfully processed by an event filter, subsequent event filters will not be responded to. At the same time, refer to the Qt help manual mentioned:
If multiple event filters are installed on a single object, the filter that was installed last is activated first.
Event filters inserted later will be responded to first. Specifically install the event filter, we will analyze it later.
-
Directly call
event
the function of the event accepting object for processing. Because it is the object of the direct callevent
, sosendEvent
the function will block and wait.// deliver the event // 直接调用对象的event函数,所以是阻塞的 consumed = receiver->event(event); return consumed
postEvent
The complete code is as follows:
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();
}
-
Determine whether the event receiving object is empty
// 事件的接收者不能为空 if (receiver == nullptr) { qWarning("QCoreApplication::postEvent: Unexpected null receiver"); delete event; return; }
-
Lock the post event list of the thread where the event receiving object is located. If it is already locked, delete the event and return to prevent leakage.
// 对事件接受对象所在线程的事件处理列表上锁 auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver); if (!locker.threadData) { // posting during destruction? just delete the event to prevent a leak delete event; return; }
-
Compress some events that can be compressed, and compress multiple events into only the last event. This is the operation of the Qt interface
update
. In order to prevent multiple refreshes from causing freezes, multiple calls in a short period of timeupdate
may only be refreshed once.// 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; }
-
Insert the event into the post event list of the thread where the receiving object is located, and wake up the event scheduler of the thread to process the event. So
postEvent
it is non-blocking, because it just inserts the event into the thread's event list, and returns after waking up the event scheduler .// 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();
How are events handled?
In Qt, the receivers of the event are all QObject
, and QObject
the event processing is to call event
the function. If the object does not handle an event at that time, it will be forwarded to the parent class event
for processing.
The event processing is mainly divided into three parts:
- First, the event loop traverses the event
- Then judge whether the event accepting object has installed the event filter (
installEventFilter
), and if so, throw the event to the event filter (eventFilter
) for processing. - If no event filter is installed or the event filter does not process the event, the event will be further forwarded to the function
event
for processing.
Therefore, in this chapter, we also analyze these three points step by step.
How is the event loop traversed?
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
The above is the main function of a classic QtGUI program, calla.exec()
int QCoreApplication::exec()
{
...
threadData->quitNow = false;
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
int returnCode = eventLoop.exec();
...
}
And looking QApplication::exec
at the source code, it actually starts an event loop ( QEventLoop
). Similarly, let's look at QEventLoop::exec
the source code, and further look at the steps to process the event.
int QEventLoop::exec(ProcessEventsFlags flags)
{
...
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCode.loadRelaxed();
}
As you can see above, QEvenLoop::exec
there is a while
loop, which is called cyclically processEvent
, and it is set WaitForMoreEvents
, that is, if there is no event, it will block and wait.
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;
}
}
Read processEvent
, it calls the event scheduler of the thread QAbstrctEventDispatcher
, and this class is an abstract base class, which has different implementations according to different platforms. Let's take ( QEventDispatcherWin32
) under windows as an example, and then analyze the process of event processing.
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());
}
As you can see, the event scheduler finally QCoreApplication
callssendPostEvents
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<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;
}
Let's analyze the blocks one by one:
-
Determine whether in a thread
if (receiver && receiver->d_func()->threadData != data) { qWarning("QCoreApplication::sendPostedEvents: Cannot send " "posted events for objects in another thread"); return; }
-
An interesting exception-safe handling that doesn't require a try/catch block
// 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);
A structure is defined CleanUp
, and the destructor ( ) of the structure ~CleanUp
saves the cleanup operations that need to be performed when the function exits. Then a structure object is created on the stack. When traversing the event list, if an exception exits, the automatically called ~CleanUp
destructor will be called.
-
send event out(
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. }
It can be seen that the core still calls sendEvent
to send the event , and sendEvent
we can see from the previous source code analysis that the event first passes through the event filter, and then passes through the event function of the object to process the event. So that leads to our next topic: event filters
event filter
In practical applications, we often need to intercept a certain event of a certain widget, such as mouse wheel scrolling, and then perform the operation we want. At this time, we can use the event filter (EventFilter
**) **
First, we need to write a eventFilter
function ourselves,
bool Class::eventFilter(QObject* watcher, QEvent* event)
{
//以过滤鼠标滚轮事件为例
if (object == m_watcherObject && event->type() == QEvent::Wheel) {
// do something
return true;
}
QWidget::eventFilter(watcher, event);
}
Then, we need to install an event filter for a certain widget to be intercepted
void Class::initUI()
{
QWidget* m_watcherObject = new QWidget(this);
// 为对象安装一个事件过滤器
m_watcherObject->installEventFilterr(this);
}
initUI();
So in what order will multiple event filters installed by an object be triggered? As we said before, the event filter installed later will be triggered first . We can get evidence of this in the source code:
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);
}
It can be clearly seen that the event filter is prepend
added to the event filter list in the form of .
Then, when a mouse wheel event is triggered, we can see that sendEvent
it will go to the event filter first. If eventFilter
a true is returned, the event will not be dispatched, otherwise, the event will be sent to other event filters If other event filters do not process the event, the event will continue to be dispatched and go to the event processing functionevent
event
Next, we come to the last stop of event processing, event
the function. This function is relatively simple. We can rewrite this function by ourselves to perform custom processing on the 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;
}
Carrying private goods time
- As mentioned before
processEvent
, add a little experience. When we sometimes have to perform time-consuming operations in the main thread loop, at this time, the interface will not be refreshed, which will cause the interface to freeze and affect the use. However, we can manually call it in this loopqApp->processEvent()
, so that all events can be manually called and processed, and the problem of stuck can be solved .