事件系统
事件系统是QT一个很重要的机制,事件循环则由QEventLoop实现。
此处拿QMenu举例。
QT源代码基于5.12分析
QMenu menu;
QAction *clear = menu.addAction(“test”);
menu.exec(QCursor::pos());
QAction *QMenu::exec(const QPoint &p, QAction *action)
{
Q_D(QMenu); // 预处理后 QMenuPrivate * const d = d_func()
// QMenu的头文件使用宏 “Q_DECLARE_PRIVATE(QMenu)" 声明了d_func
ensurePolished();
createWinId();
QEventLoop eventLoop;
d->eventLoop = &eventLoop;
popup(p, action);
QPointer<QObject> guard = this;
(void) eventLoop.exec(); // 此处执行后会阻塞当前线程,进入事件循环
if (guard.isNull())
return 0;
action = d->syncAction;
d->syncAction = 0;
d->eventLoop = 0;
return action;
}
下面步入正题,QEventLoop的设计思想
class Q_CORE_EXPORT QEventLoop : public QObject
{
Q_OBJECT
Q_DECLARE_PRIVATE(QEventLoop)
public:
explicit QEventLoop(QObject *parent = nullptr);
~QEventLoop();
enum ProcessEventsFlag {
AllEvents = 0x00,
ExcludeUserInputEvents = 0x01,
ExcludeSocketNotifiers = 0x02,
WaitForMoreEvents = 0x04,
X11ExcludeTimers = 0x08,
EventLoopExec = 0x20,
DialogExec = 0x40
};
Q_DECLARE_FLAGS(ProcessEventsFlags, ProcessEventsFlag)
bool processEvents(ProcessEventsFlags flags = AllEvents);
void processEvents(ProcessEventsFlags flags, int maximumTime);
int exec(ProcessEventsFlags flags = AllEvents);
void exit(int returnCode = 0);
bool isRunning() const;
void wakeUp();
bool event(QEvent *event) override;
public Q_SLOTS:
void quit();
};
QEventLoop::QEventLoop(QObject *parent)
: QObject(*new QEventLoopPrivate, parent)
{
Q_D(QEventLoop);
if (!QCoreApplication::instance() && QCoreApplicationPrivate::threadRequiresCoreApplication()) {
qWarning("QEventLoop: Cannot be used without QApplication");
} else {
d->threadData->ensureEventDispatcher();
}
}
QEventLoop不会创建线程,但是其执行会借用调用线程来完成事件循环,QEventLoop的构造会保证其有一个事件分发器,对于不同平台其事件分发器调用的底层API也不相同,事件分发器包括QEventDispatcherUNIX,QEventDispatcherGlib,QEventDispatcherWin32,QEventDispatcherWinRT。在此以QEventDispatcherUNIX为例进行分析
int QEventLoop::exec(ProcessEventsFlags flags)
{
QEventLoopPrivate * const d = d_func();
//we need to protect from race condition with QThread::exit
QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);
if (d->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);
++d->threadData->loopLevel;
d->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();
QEventLoop *eventLoop = d->threadData->eventLoops.pop();
Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
Q_UNUSED(eventLoop); // --release warning
d->inExec = false;
--d->threadData->loopLevel;
}
};
LoopReference ref(d, locker); // C++ RAII机制,用来退出后的清理操作
// 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 (d->threadData->loopLevel > 1)
emscripten_sleep(1);
#endif
// 事件循环
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCode.load();
}
processEvents会调用分发器的processEvents
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop); // 预处理后 QEventLoopPrivate * const d = d_func()
if (!d->threadData->hasEventDispatcher())
return false;
return d->threadData->eventDispatcher.load()->processEvents(flags);
}
bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherUNIX);
d->interrupt.store(0);
// we are awake, broadcast it
emit awake();
QCoreApplicationPrivate::sendPostedEvents(0, 0, d->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 = (d->threadData->canWaitLocked()
&& !d->interrupt.load()
&& wait_for_events);
if (canWait)
emit aboutToBlock();
if (d->interrupt.load())
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);
}
qt_safe_poll会调用系统底层API,对于UNIX,其调用的是select函数,调用路径
qt_safe_poll->qt_ppoll->qt_poll->select
QT需要通过定义宏#define QT_FEATURE_poll_poll 1
配置poll才能使用poll
处理哪些事件
QEventLoop::processEvents会处理套接字及QEventLoop创建的事件描述符以及定时事件,自建的事件描述符一般是通过eventfd创建,用来唤醒阻塞在select的线程
问题遗留
暂时未找到调用QEventLoop::exit的位置,从程序现象来看,当鼠标右击,在其他位置在次右击时会先打断之前的菜单,在创建一个新的。找到这块代码分析,则才是一个完整事件系统