文章目录
- 主事件循环
- 事件循环
- 事件调度器
- 事件处理
- 投递事件
- 发送事件
- 事件循环的嵌套
- 线程的事件循环
- deleteLater与事件循环
- QEventLoop类
- QEventLoop应用
- 等待一段时间
- 同步操作
- 模拟模态对话框
- 参考
本文主要对QT中的事件循环做简单介绍和使用
Qt作为一个跨平台的UI框架,其事件循环实现原理, 就是把不同平台的事件循环进行了封装,并提供统一的抽象接口。
主事件循环
一个最简单的qt程序:
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
return app.exec();
}
在QCoreApplication app(argc, argv)
这一步,主要是创建并初始化了一个QCoreApplication
对象。最主要的是设置一些线程相关的数据(QThreadData),比如事件调度器(QAbstractEventDispatcher)等。
而app.exec()
则是为了启动一个事件循环来分发事件。如果没有事件循环或事件循环没有启动,则对象永远不会接收到事件。
由QCoreApplication启动的事件循环也叫作主事件循环,所有的事件分发、事件处理都从这里开始。
查看exec的代码(qtbase/src/corelib/kernel/qcoreapplication.cpp
):
int QCoreApplication::exec()
{
if (!QCoreApplicationPrivate::checkInstance("exec"))
return -1;
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
if (!threadData->eventLoops.isEmpty()) {
qWarning("QCoreApplication::exec: The event loop is already running");
return -1;
}
threadData->quitNow = false;
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
int returnCode = eventLoop.exec();
threadData->quitNow = false;
if (self)
self->d_func()->execCleanup();
return returnCode;
}
可以看到,基本就是启动一个QEventLoop。
事件循环
继续查看QEventLoop的代码(qtbase/src/corelib/kernel/qeventloop.cpp
):
int QEventLoop::exec(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
auto threadData = d->threadData.loadRelaxed();
//we need to protect from race condition with QThread::exit
QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex);
if (threadData->quitNow)
return -1;
if (d->inExec) {
qWarning("QEventLoop::exec: instance %p has already called exec()", this);
return -1;
}
struct LoopReference {
QEventLoopPrivate *d;
QMutexLocker &locker;
bool exceptionCaught;
LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)
{
d->inExec = true;
d->exit.storeRelease(false);
auto threadData = d->threadData.loadRelaxed();
++threadData->loopLevel;
threadData->eventLoops.push(d->q_func());
locker.unlock();
}
~LoopReference()
{
if (exceptionCaught) {
qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
"exceptions from an event handler is not supported in Qt.\n"
"You must not let any exception whatsoever propagate through Qt code.\n"
"If that is not possible, in Qt 5 you must at least reimplement\n"
"QCoreApplication::notify() and catch all exceptions there.\n");
}
locker.relock();
auto threadData = d->threadData.loadRelaxed();
QEventLoop *eventLoop = threadData->eventLoops.pop();
Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
Q_UNUSED(eventLoop); // --release warning
d->inExec = false;
--threadData->loopLevel;
}
};
LoopReference ref(d, locker);
// remove posted quit events when entering a new event loop
QCoreApplication *app = QCoreApplication::instance();
if (app && app->thread() == thread())
QCoreApplication::removePostedEvents(app, QEvent::Quit);
#ifdef Q_OS_WASM
// Partial support for nested event loops: Make the runtime throw a JavaSrcript
// exception, which returns control to the browser while preserving the C++ stack.
// Event processing then continues as normal. The sleep call below never returns.
// QTBUG-70185
if (threadData->loopLevel > 1)
emscripten_sleep(1);
#endif
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCode.loadRelaxed();
}
可见QEventLoop::exec()是通过循环不断地调用QEventLoop::processEvents()来分发事件队列中的事件。
而最终完成事件分发的是事件调度器。通过下面的代码我们可以看出,事件调度器存在于各自线程相关数据中。也就是说每个线程都可以有、并且只使用自己专属的事件调度器。
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
auto threadData = d->threadData.loadRelaxed();
if (!threadData->hasEventDispatcher())
return false;
return threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}
事件调度器
事件调度器就比较依赖于各个平台的实现了,各平台上的事件调度器实现都不尽相同。Linux下是QEventDispatcherUNIX:
bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherUNIX);
d->interrupt.storeRelaxed(0);
// we are awake, broadcast it
emit awake();
auto threadData = d->threadData.loadRelaxed();
QCoreApplicationPrivate::sendPostedEvents(nullptr, 0, threadData);
const bool include_timers = (flags & QEventLoop::X11ExcludeTimers) == 0;
const bool include_notifiers = (flags & QEventLoop::ExcludeSocketNotifiers) == 0;
const bool wait_for_events = flags & QEventLoop::WaitForMoreEvents;
const bool canWait = (threadData->canWaitLocked()
&& !d->interrupt.loadRelaxed()
&& wait_for_events);
if (canWait)
emit aboutToBlock();
if (d->interrupt.loadRelaxed())
return false;
timespec *tm = nullptr;
timespec wait_tm = { 0, 0 };
if (!canWait || (include_timers && d->timerList.timerWait(wait_tm)))
tm = &wait_tm;
d->pollfds.clear();
d->pollfds.reserve(1 + (include_notifiers ? d->socketNotifiers.size() : 0));
if (include_notifiers)
for (auto it = d->socketNotifiers.cbegin(); it != d->socketNotifiers.cend(); ++it)
d->pollfds.append(qt_make_pollfd(it.key(), it.value().events()));
// This must be last, as it's popped off the end below
d->pollfds.append(d->threadPipe.prepare());
int nevents = 0;
switch (qt_safe_poll(d->pollfds.data(), d->pollfds.size(), tm)) {
case -1:
perror("qt_safe_poll");
break;
case 0:
break;
default:
nevents += d->threadPipe.check(d->pollfds.takeLast());
if (include_notifiers)
nevents += d->activateSocketNotifiers();
break;
}
if (include_timers)
nevents += d->activateTimers();
// return true if we handled events, false otherwise
return (nevents > 0);
}
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData)会派发当前线程事件队列中所有的事件。
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;
}
++data->postEventList.recursion;
auto locker = qt_unique_lock(data->postEventList.mutex);
// by default, we assume that the event dispatcher can go to sleep after
// processing all events. if any new events are posted while we send
// events, canWait will be set to false.
data->canWait = (data->postEventList.size() == 0);
if (data->postEventList.size() == 0 || (receiver && !receiver->d_func()->postedEvents)) {
--data->postEventList.recursion;
return;
}
data->canWait = true;
// okay. here is the tricky loop. be careful about optimizing
// this, it looks the way it does for good reasons.
int startOffset = data->postEventList.startOffset;
int &i = (!event_type && !receiver) ? data->postEventList.startOffset : startOffset;
data->postEventList.insertionOffset = data->postEventList.size();
// 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()) {
// avoid live-lock
if (i >= data->postEventList.insertionOffset)
break;
const QPostEvent &pe = data->postEventList.at(i);
++i;
if (!pe.event)
continue;
if ((receiver && receiver != pe.receiver) || (event_type && event_type != pe.event->type())) {
data->canWait = false;
continue;
}
if (pe.event->type() == QEvent::DeferredDelete) {
// DeferredDelete events are sent either
// 1) when the event loop that posted the event has returned; or
// 2) if explicitly requested (with QEvent::DeferredDelete) for
// events posted by the current event loop; or
// 3) if the event was posted before the outermost event loop.
int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel();
int loopLevel = data->loopLevel + data->scopeLevel;
const bool allowDeferredDelete =
(eventLevel > loopLevel
|| (!eventLevel && loopLevel > 0)
|| (event_type == QEvent::DeferredDelete
&& eventLevel == loopLevel));
if (!allowDeferredDelete) {
// cannot send deferred delete
if (!event_type && !receiver) {
// we must copy it first; we want to re-post the event
// with the event pointer intact, but we can't delay
// nulling the event ptr until after re-posting, as
// addEvent may invalidate pe.
QPostEvent pe_copy = pe;
// null out the event so if sendPostedEvents recurses, it
// will ignore this one, as it's been re-posted.
const_cast<QPostEvent &>(pe).event = nullptr;
// re-post the copied event so it isn't lost
data->postEventList.addEvent(pe_copy);
}
continue;
}
}
// 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;
}
事件处理
Application还提供了sendEvent和poseEvent两个函数,分别用来发送事件。sendEvent发出的事件会立即被处理,也就是“同步”执行。postEvent发送的事件会被加入事件队列,在下一轮事件循环时才处理,也就是“异步”执行。还有一个特殊的sendPostedEvents,是将已经加入队列中的准备异步执行的事件立即同步执行。
QCoreApplication::sendPostedEvents()会清空事件队列并立即返回。
void QCoreApplication::sendPostedEvents(QObject *receiver, int event_type)
{
// ### Qt 6: consider splitting this method into a public and a private
// one, so that a user-invoked sendPostedEvents can be detected
// and handled properly.
QThreadData *data = QThreadData::current();
QCoreApplicationPrivate::sendPostedEvents(receiver, event_type, data);
}
而QCoreApplication::processEvents()相较于QCoreApplication::sendPostedEvents()相当于多了等待事件发生这一步。也就是说它在清空事件队列后不会立即返回。
void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags)
{
QThreadData *data = QThreadData::current();
if (!data->hasEventDispatcher())
return;
data->eventDispatcher.loadRelaxed()->processEvents(flags);
}
投递事件
当通过QCoreApplication::postEvent()投递一个事件的时候,会将其加入到事件队列中,并唤醒事件调度器。具体来说,是向eventfd()系统调用获取的文件描述符写入数据,使其状态为数据可读(POLLIN)。
You can manually post events to any object in any thread at any time using the thread-safe function QCoreApplication::postEvent(). The events will automatically be dispatched by the event loop of the thread where the object was created.
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();
}
发送事件
postEvent()、sendEvent()是Qt事件循环中两个不同的阶段。如果简化掉一切中间流程,可以认为QCoreApplication::sendEvent()是在直接调用接受者的QObject::event()方法(QGuiApplication及QApplication实现不尽相同)。
Event filters are supported in all threads, with the restriction that the monitoring object must live in the same thread as the monitored object. Similarly, QCoreApplication::sendEvent() (unlike postEvent()) can only be used to dispatch events to objects living in the thread from which the function is called
bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{
Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());
if (event)
event->spont = false;
return notifyInternal2(receiver, event);
}
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
if (!self && selfRequired)
return false;
// Make it possible for Qt Script to hook into events even
// though QApplication is subclassed...
bool result = false;
void *cbdata[] = { receiver, event, &result };
if (QInternal::activateCallbacks(QInternal::EventNotifyCallback, cbdata)) {
return result;
}
// 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.
QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData;
QScopedScopeLevelCounter scopeLevelCounter(threadData);
if (!selfRequired)
return doNotify(receiver, event);
return self->notify(receiver, event);
}
bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{
// no events are delivered after ~QCoreApplication() has started
if (QCoreApplicationPrivate::is_app_closing)
return true;
return doNotify(receiver, event);
}
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
return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}
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
consumed = receiver->event(event);
return consumed;
}
事件循环的嵌套
事件循环是可以嵌套的,当在子事件循环中的时候,父事件循环中的事件实际上处于中断状态,当子循环跳出exec之后才可以执行父循环中的事件。当然,这不代表在执行子循环的时候,类似父循环中的界面响应会被中断,因为往往子循环中也会有父循环的大部分事件,执行QMessageBox::exec(),QEventLoop::exec()的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是由于GUI界面的响应已经被包含到子循环中了,所以GUI界面依然能够得到响应。如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出。
如果子widget没有accept或ignore该事件,则该事件会被传递给其父窗口。那么:对于一个继承而来的类,只要我们重写实现了其各个事件处理函数,则对应的事件肯定无法传递给其父widget! 哪怕重写的该事件处理函数的函数体为空!如果是标准的控件对象,则其肯定没重写各个事件处理函数。那消息能不能传递到父widget中,则取决于中途有没有使用事件过滤器等将该信号拦截下来了。
案例:
在一个QWidget上建了一个QLabel。而后实现父QWidget的mousePressEvent(), 然后跟一下发现:当我click这个label时:居然能进入到父QWidget的mousePressEvent()中,但是如果把子改成QPushButton则进入不了。
(1)对于QLabel: 如果不重写mouse处理函数,也没有设置事件过滤器等操作的话,则相当于:其对mouse这个事件一直没有进行处理,那没有进行处理的话,相当于上边所说的情况,此时该事件会被传递给其parent。
(2)而对于QPushButton而言:当click它时:其会发射clicked()信号,其实这就相当于它对该事件的一个operator过程。所以:这里它accept该事件并进行了对应处理。从而:无法传递给其父窗口。
线程的事件循环
Threads and QObjects
QThread::exec()将开启一个新的事件循环,每个线程的事件循环独立,并且只为存在于其线程内的QObjects对象传递事件,这些对象分为两类,一类是在其线程内创建的,一类是通过moveToThread()移动到该线程内的。可通过thread()方法查看对象依附的线程(thread affinity)。
对象、线程、事件循环的关系如下图:
deleteLater与事件循环
deleteLater()将发送一个QEvent::DeferredDelete事件到事件循环中。
deleteLater()真正删除对象:
- 当控制返回事件循环时,该对象将被删除
- 如果调用deleteLater()时事件循环未运行,一旦事件循环开始,该对象将被删除
- 如果在循环停止后调用deleteLater(),该对象将在线程完成时被销毁(Qt 4.8以后)
注意,进入和离开新的事件循环(例如QDialog::exec())将不会执行延迟删除,控制权必须返回到调用deleteLater()时所在的事件循环时,对象才会被删除。
QEventLoop类
QEventLoop即Qt中的事件循环类,主要接口如下:
// 进入事件循环直到exit被调用
int exec(QEventLoop::ProcessEventsFlags flags = AllEvents)
// 通知事件循环退出
void exit(int returnCode = 0)
// 判断事件循环是否运行
bool isRunning() const
// 处理符合标志的待处理事件,直到没有更多事件需要处理为止。
bool processEvents(QEventLoop::ProcessEventsFlags flags = AllEvents)
// 处理与标志匹配且最多处理 maxTime 毫秒的待处理事件,或者直到没有更多事件可处理为止,以较短者为准。如果有一个长时间运行的操作,并且希望在不允许用户输入的情况下显示其进度(即通过使用 ExcludeUserInputEvents 标志),则此函数特别有用。
// 注意事项:
// 此函数不会持续处理事件;它会在所有可用事件处理完毕后返回。
// 指定 WaitForMoreEvents 标志没有意义,将被忽略。
void processEvents(QEventLoop::ProcessEventsFlags flags, int maxTime)
// 唤醒事件循环
void wakeUp()
QEventLoop应用
等待一段时间
QEventLoop loop;
QTimer::singleShot(1000, &loop, SLOT(quit()));
qInfo() << QTime::currentTime();
loop.exec();
qInfo() << QTime::currentTime();
同步操作
经常会有这种场景: “触发 ”了某项操作,必须等该操作完成后才能进行“ 下一步 ”,但在等待该操作完成期间不能阻塞主线程。比如:数据获取,向服务器发起登录请求后,必须等收到服务器返回的数据,才决定下一步如何执行。这种场景,如果设计成异步调用,直接用Qt的信号/槽即可,如果要设计成同步调用,就可以使用本地QEventLoop。
void A::onFinish()
{
//槽中退出事件循环
loop.quit();
}
void A::work()
{
// 1.声明本地EventLoop
QEventLoop loop;
// 2. 先连接好信号
connect(pwaitPeer, &waitPeer::finished,this,&A::onFinish);
// 3. 发起操作
...
// 4. 启动事件循环。阻塞当前函数调用,但是事件循环还能运行。这里不会再往下运行,直到前面的槽中,调用loop.quit之后,才会继续往下走
loop.exec();
}
模拟模态对话框
QDialog dlg;
dlg.show();
QEventLoop loop;
connect(&dlg, SIGNAL(finished(int)), &loop, SLOT(quit()));
loop.exec(QEventLoop::ExcludeUserInputEvents);
参考
QEventLoop
Threads and QObjects