Qt之QEventLoop浅探

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 用于立即发送事件并直接调用事件处理函数。这种方法是同步的,意味着事件在函数调用时立即处理,并且控制在事件处理完毕后才返回。这种方法适用于希望立即处理事件的情况。用于立即发送事件并直接调用事件处理函数。这种方法是同步的,意味着事件在函数调用时立即处理,并且控制在事件处理完毕后才返回。这种方法适用于希望立即处理事件的情况。

  • 立即处理事件,并且需要立即获得处理结果。
  • 当事件处理是当前任务的关键部分,需要在当前上下文中完成。
  • 当确定事件处理不会导致递归或阻塞时。
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Qt 事件和信号的关系 Qt的事件是windows的底层消息封装而成的。这个消息和MFC里的消息是同一概念,都是指...
    行走的代码阅读 1,568评论 0 0
  • 相同点 目的都是为了解决对象间通信的问题。 事件发生或信号发射时会有相应的处理函数。 不同之处 返回值事件有返回值...
    riverlet阅读 2,041评论 0 0
  • Qt笔记总结 作者:hackett 微信公众号:加班猿 一、常用控件 按钮类 QPushButton QtoolB...
    加班猿阅读 668评论 0 1
  • 简介 这里简单介绍Qt的一些核心机制,具体参见Qt文档。 主要包含内容: Qt的信号和槽,以及事件机制 Qt Ob...
    QuietHeart阅读 1,633评论 0 3
  • QT学习笔记 1.对象树:在Qt中,每个 QObject 内部都有一个list,用来保存所有的 children,...
    油炸花生米1阅读 747评论 0 1