Qt官方文档对QEventLoop描述,在任何时候,都可以创建一个 QEventLoop 对象并调用该对象上的exec()来启动本地事件循环。在事件循环中,调用exit()将强制exec()返回。
类似的,QDialog::exec和QApplication::exec,内部都是创建QEventLoop进行事件循环,并且执行eventLoop.exec(),根据需要在exec()时传入想要处理的事件类型ProcessEventsFlags。
QAbstractEventDispatcher
QEventLoop可以在需要时创建并执行exec阻塞程序,从代码上看来,QEventLoop的exec确实会使用while来阻塞循环,但是processEvents中的事件却依赖与当前线程中的QAbstractEventDispatcher。在QEventLoop的构造函数中,会先判断当前线程是否有QCoreApplication::instance(),才会通过d->threadData->ensureEventDispatcher()去创建QAbstractEventDispatcher。(一个线程只有一个QAbstractEventDispatcher,即有QCoreApplication实例的线程才有QAbstractEventDispatcher)
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();
}
}
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
processEvents的源码如下,获取当前线程的EventDispatcher,QEventLoop::processEvents实际上是一个wapper,真正处理事件的是线程中的QAbstractEventDispatcher。
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher())
return false;
return d->threadData->eventDispatcher.load()->processEvents(flags);
}
QThreadData 是 Qt 框架内部的一个类,用于管理和存储与线程相关的数据。它通常不用于直接操作,但理解它对于深入掌握 Qt 的多线程机制非常有帮助。QThreadData 的实例在每个线程中都是唯一的,并且包含与该线程相关的信息和状态。QThreadData 的作用QThreadData 管理与 QThread 实例和事件循环相关的数据。它在 Qt 的多线程机制中起着关键作用,包括:
线程管理: 保存与线程相关的状态信息。
事件循环: 管理线程的事件循环状态和消息队列。
线程局部存储: 提供每个线程独立的数据存储。
互斥和同步: 协调线程之间的互斥和同步操作。
在QCoreApplicationPrivate::init()创建并初始化时,创建当前线程中的QAbstractEventDispatcher 的派生对象QWindowsGuiEventDispatcher
QAbstractEventDispatcher * QWindowsIntegration::createEventDispatcher() const
{
return new QWindowsGuiEventDispatcher;
}
所有QEventLoop::exec()中实际处理线程中的QWindowsGuiEventDispatcher
bool QWindowsGuiEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags)
{
const QEventLoop::ProcessEventsFlags oldFlags = m_flags;
m_flags = flags;
const bool rc = QEventDispatcherWin32::processEvents(flags);
m_flags = oldFlags;
return rc;
}
QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)的相关处理流程如下
初始化
初始化调度器和句柄:
Q_D(QEventDispatcherWin32) 初始化调度器。
如果 d->internalHwnd 未设置,则调用 createInternalHwnd() 创建它,并调用 wakeUp() 触发 sendPostedEvents() 的调用。
事件循环设置:
d->interrupt.store(false); 确保中断标志为 false。
发出 awake() 信号,表示事件循环处于唤醒状态。
主要事件处理循环
循环变量:
canWait: 控制循环是否可以等待更多事件。
retVal: 跟踪是否处理了任何事件。
seenWM_QT_SENDPOSTEDEVENTS: 跟踪是否看到 WM_QT_SENDPOSTEDEVENTS 消息。
needWM_QT_SENDPOSTEDEVENTS: 标记是否需要发送 WM_QT_SENDPOSTEDEVENTS。消息处理:
Peek 和处理消息:
如果队列中有用户输入事件或套接字事件,则处理这些事件。
如果没有,则 PeekMessage(&msg, 0, 0, 0, PM_REMOVE) 检查 Windows 消息队列。
如果找到消息并且它们符合排除标志,则将它们重新排队。
消息处理:
对于每个消息 (msg):
如果消息是 WM_QT_SENDPOSTEDEVENTS 并且已经看到过,则设置 needWM_QT_SENDPOSTEDEVENTS 为 true。
对于 WM_TIMER,通过跳过已处理的计时器来避免活锁。
如果收到 WM_QUIT,应用程序退出。
如果消息未被过滤,则对其进行翻译和分派。
事件通知器和等待:
如果没有找到消息,则调用 MsgWaitForMultipleObjectsEx 等待消息或信号对象。
如果仍然没有,则循环可以等待 (canWait)。
在等待之前发出 aboutToBlock() 信号,等待之后发出 awake() 信号。
后处理
后事件处理:
如果未看到 WM_QT_SENDPOSTEDEVENTS 且循环未执行,则手动调用 sendPostedEvents()。
如果设置了 needWM_QT_SENDPOSTEDEVENTS,则通过 PostMessage 发送 WM_QT_SENDPOSTEDEVENTS 消息。
返回值:
函数返回 retVal,表示是否处理了任何事件。
事件循环调度器QAbstractEventDispatcher一般一个线程只存在一个,在主线程(GUI线程)中,app的exec和其他事件循环起的exec都会处理事件循环队列中的事件,包括处理从系统消息队列中取出的msg。不同的exec的区别在于传入flag,不同的flag会在QAbstractEventDispatcher中“关心”或“不关心”的消息类型进行处理。
事件循环中,会分发消息,其中QEvent的发送的消息msg类型为WM_QT_SENDPOSTEDEVENTS,收到系统的WM_QT_SENDPOSTEDEVENTS时,会从QThreadData中的QPostEventList postEventList取出事件进行处理
if (!filterNativeEvent(false, QAbstractNativeEventFilter::WinProcessMsg, QByteArrayLiteral("windows_generic_MSG"), &msg, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DispatchMessage
DispatchMessage(&msg) 函数将消息分派到与消息关联的窗口过程(Window Procedure)。窗口过程是一个回调函数,它处理窗口接收到的所有消息。每个窗口都有一个与之关联的窗口过程,负责处理例如绘制、输入、命令等各种消息。
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_PAINT:
// 处理绘制消息
break;
case WM_DESTROY:
// 处理窗口销毁消息
PostQuitMessage(0);
break;
case WM_KEYDOWN:
// 处理按键消息
break;
// 其他消息处理
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
return 0;
}
在创建窗口时,需要注册一个窗口类,其中包括指定窗口过程。例如:
WNDCLASS wc = {0};
wc.lpfnWndProc = WindowProc; // 设置窗口过程
wc.hInstance = hInstance;
wc.lpszClassName = L"SampleWindowClass";
RegisterClass(&wc);
HWND hwnd = CreateWindowEx(
0,
wc.lpszClassName,
L"Sample Window",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
在DispatchMessage后会触发qt_internal_proc处理消息
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
case WM_QT_SENDPOSTEDEVENTS: {
const int localSerialNumber = d->serialNumber.load();
if (localSerialNumber != d->lastSerialNumber) {
d->lastSerialNumber = localSerialNumber;
q->sendPostedEvents();
}
系统的事件循环队列
QWindowSystemInterfacePrivate::WindowSystemEventList QWindowSystemInterfacePrivate::windowSystemEventQueue;
WM_QT_SENDPOSTEDEVENTS --> QWindowSystemInterfacePrivate::Mouse -->QEvent::MouseButtonDblClick --> QEvent::MouseButtonRelease --> QAbstractButton::clicked->slot
鼠标事件会记录鼠标移动的位置,记录鼠标所在的window,在触发鼠标点击或滚轮等系统事件时,会获取到鼠标当前位置的widget,通过QGuiApplication::sendSpontaneousEvent(window, &ev);将对应鼠标事件发送给window,window接收后再具体发送到window上的某一QObject上
const QPostEvent &pe = data->postEventList.at(i);
QCoreApplication::sendEvent(r, e);
QApplication::postEvent 和 QApplication::sendEvent
是 Qt 中用于事件处理的两种不同方法。它们的主要区别在于事件的处理时机。
QApplication::postEvent
与qt发送给系统的WM_QT_SENDPOSTEDEVENTS相关
QApplication::postEvent 用于将事件添加到事件队列中,稍后由 Qt 事件循环处理。该方法是异步的,意味着它不会立即处理事件,而是等待控制返回到事件循环时再进行处理。这种方法适用于希望在不干扰当前执行流的情况下发送事件的情况。
- 将事件推迟到事件循环中处理,以便当前任务可以继续执行。
- 希望避免递归调用或潜在的长时间阻塞。
- 当事件处理不需要立即完成时。
QApplication::sendEvent
QApplication::sendEvent 用于立即发送事件并直接调用事件处理函数。这种方法是同步的,意味着事件在函数调用时立即处理,并且控制在事件处理完毕后才返回。这种方法适用于希望立即处理事件的情况。用于立即发送事件并直接调用事件处理函数。这种方法是同步的,意味着事件在函数调用时立即处理,并且控制在事件处理完毕后才返回。这种方法适用于希望立即处理事件的情况。
- 立即处理事件,并且需要立即获得处理结果。
- 当事件处理是当前任务的关键部分,需要在当前上下文中完成。
- 当确定事件处理不会导致递归或阻塞时。