Qt官方文檔對QEventLoop描述,在任何時候携狭,都可以創(chuàng)建一個 QEventLoop 對象并調(diào)用該對象上的exec()來啟動本地事件循環(huán)。在事件循環(huán)中艇挨,調(diào)用exit()將強制exec()返回正塌。
類似的,QDialog::exec和QApplication::exec陪捷,內(nèi)部都是創(chuàng)建QEventLoop進行事件循環(huán),并且執(zhí)行eventLoop.exec()诺擅,根據(jù)需要在exec()時傳入想要處理的事件類型ProcessEventsFlags市袖。
QAbstractEventDispatcher
QEventLoop可以在需要時創(chuàng)建并執(zhí)行exec阻塞程序,從代碼上看來烁涌,QEventLoop的exec確實會使用while來阻塞循環(huán)苍碟,但是processEvents中的事件卻依賴與當(dāng)前線程中的QAbstractEventDispatcher。在QEventLoop的構(gòu)造函數(shù)中撮执,會先判斷當(dāng)前線程是否有QCoreApplication::instance()微峰,才會通過d->threadData->ensureEventDispatcher()去創(chuàng)建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的源碼如下蜓肆,獲取當(dāng)前線程的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 框架內(nèi)部的一個類症概,用于管理和存儲與線程相關(guān)的數(shù)據(jù)。它通常不用于直接操作早芭,但理解它對于深入掌握 Qt 的多線程機制非常有幫助彼城。QThreadData 的實例在每個線程中都是唯一的,并且包含與該線程相關(guān)的信息和狀態(tài)退个。QThreadData 的作用QThreadData 管理與 QThread 實例和事件循環(huán)相關(guān)的數(shù)據(jù)募壕。它在 Qt 的多線程機制中起著關(guān)鍵作用,包括:
線程管理: 保存與線程相關(guān)的狀態(tài)信息帜乞。
事件循環(huán): 管理線程的事件循環(huán)狀態(tài)和消息隊列司抱。
線程局部存儲: 提供每個線程獨立的數(shù)據(jù)存儲筐眷。
互斥和同步: 協(xié)調(diào)線程之間的互斥和同步操作黎烈。
在QCoreApplicationPrivate::init()創(chuàng)建并初始化時,創(chuàng)建當(dāng)前線程中的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)的相關(guān)處理流程如下
初始化
初始化調(diào)度器和句柄:
Q_D(QEventDispatcherWin32) 初始化調(diào)度器匀谣。
如果 d->internalHwnd 未設(shè)置照棋,則調(diào)用 createInternalHwnd() 創(chuàng)建它,并調(diào)用 wakeUp() 觸發(fā) sendPostedEvents() 的調(diào)用武翎。
事件循環(huán)設(shè)置:
d->interrupt.store(false); 確保中斷標(biāo)志為 false烈炭。
發(fā)出 awake() 信號,表示事件循環(huán)處于喚醒狀態(tài)宝恶。
主要事件處理循環(huán)
循環(huán)變量:
canWait: 控制循環(huán)是否可以等待更多事件符隙。
retVal: 跟蹤是否處理了任何事件。
seenWM_QT_SENDPOSTEDEVENTS: 跟蹤是否看到 WM_QT_SENDPOSTEDEVENTS 消息垫毙。
needWM_QT_SENDPOSTEDEVENTS: 標(biāo)記是否需要發(fā)送 WM_QT_SENDPOSTEDEVENTS霹疫。消息處理:
Peek 和處理消息:
如果隊列中有用戶輸入事件或套接字事件,則處理這些事件综芥。
如果沒有丽蝎,則 PeekMessage(&msg, 0, 0, 0, PM_REMOVE) 檢查 Windows 消息隊列。
如果找到消息并且它們符合排除標(biāo)志膀藐,則將它們重新排隊屠阻。
消息處理:
對于每個消息 (msg):
如果消息是 WM_QT_SENDPOSTEDEVENTS 并且已經(jīng)看到過,則設(shè)置 needWM_QT_SENDPOSTEDEVENTS 為 true额各。
對于 WM_TIMER国觉,通過跳過已處理的計時器來避免活鎖。
如果收到 WM_QUIT虾啦,應(yīng)用程序退出麻诀。
如果消息未被過濾蚜枢,則對其進行翻譯和分派。
事件通知器和等待:
如果沒有找到消息针饥,則調(diào)用 MsgWaitForMultipleObjectsEx 等待消息或信號對象厂抽。
如果仍然沒有,則循環(huán)可以等待 (canWait)丁眼。
在等待之前發(fā)出 aboutToBlock() 信號筷凤,等待之后發(fā)出 awake() 信號。
后處理
后事件處理:
如果未看到 WM_QT_SENDPOSTEDEVENTS 且循環(huán)未執(zhí)行苞七,則手動調(diào)用 sendPostedEvents()藐守。
如果設(shè)置了 needWM_QT_SENDPOSTEDEVENTS,則通過 PostMessage 發(fā)送 WM_QT_SENDPOSTEDEVENTS 消息蹂风。
返回值:
函數(shù)返回 retVal卢厂,表示是否處理了任何事件。
事件循環(huán)調(diào)度器QAbstractEventDispatcher一般一個線程只存在一個惠啄,在主線程(GUI線程)中慎恒,app的exec和其他事件循環(huán)起的exec都會處理事件循環(huán)隊列中的事件,包括處理從系統(tǒng)消息隊列中取出的msg撵渡。不同的exec的區(qū)別在于傳入flag融柬,不同的flag會在QAbstractEventDispatcher中“關(guān)心”或“不關(guān)心”的消息類型進行處理。
事件循環(huán)中趋距,會分發(fā)消息粒氧,其中QEvent的發(fā)送的消息msg類型為WM_QT_SENDPOSTEDEVENTS,收到系統(tǒng)的WM_QT_SENDPOSTEDEVENTS時节腐,會從QThreadData中的QPostEventList postEventList取出事件進行處理
if (!filterNativeEvent(false, QAbstractNativeEventFilter::WinProcessMsg, QByteArrayLiteral("windows_generic_MSG"), &msg, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DispatchMessage
DispatchMessage(&msg) 函數(shù)將消息分派到與消息關(guān)聯(lián)的窗口過程(Window Procedure)外盯。窗口過程是一個回調(diào)函數(shù),它處理窗口接收到的所有消息翼雀。每個窗口都有一個與之關(guān)聯(lián)的窗口過程饱苟,負(fù)責(zé)處理例如繪制、輸入锅纺、命令等各種消息掷空。
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;
}
在創(chuàng)建窗口時,需要注冊一個窗口類囤锉,其中包括指定窗口過程坦弟。例如:
WNDCLASS wc = {0};
wc.lpfnWndProc = WindowProc; // 設(shè)置窗口過程
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后會觸發(fā)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();
}
系統(tǒng)的事件循環(huán)隊列
QWindowSystemInterfacePrivate::WindowSystemEventList QWindowSystemInterfacePrivate::windowSystemEventQueue;
WM_QT_SENDPOSTEDEVENTS --> QWindowSystemInterfacePrivate::Mouse -->QEvent::MouseButtonDblClick --> QEvent::MouseButtonRelease --> QAbstractButton::clicked->slot
鼠標(biāo)事件會記錄鼠標(biāo)移動的位置,記錄鼠標(biāo)所在的window官地,在觸發(fā)鼠標(biāo)點擊或滾輪等系統(tǒng)事件時酿傍,會獲取到鼠標(biāo)當(dāng)前位置的widget,通過QGuiApplication::sendSpontaneousEvent(window, &ev);將對應(yīng)鼠標(biāo)事件發(fā)送給window驱入,window接收后再具體發(fā)送到window上的某一QObject上
const QPostEvent &pe = data->postEventList.at(i);
QCoreApplication::sendEvent(r, e);
QApplication::postEvent 和 QApplication::sendEvent
是 Qt 中用于事件處理的兩種不同方法赤炒。它們的主要區(qū)別在于事件的處理時機氯析。
QApplication::postEvent
與qt發(fā)送給系統(tǒng)的WM_QT_SENDPOSTEDEVENTS相關(guān)
QApplication::postEvent 用于將事件添加到事件隊列中,稍后由 Qt 事件循環(huán)處理莺褒。該方法是異步的掩缓,意味著它不會立即處理事件,而是等待控制返回到事件循環(huán)時再進行處理遵岩。這種方法適用于希望在不干擾當(dāng)前執(zhí)行流的情況下發(fā)送事件的情況你辣。
- 將事件推遲到事件循環(huán)中處理,以便當(dāng)前任務(wù)可以繼續(xù)執(zhí)行尘执。
- 希望避免遞歸調(diào)用或潛在的長時間阻塞舍哄。
- 當(dāng)事件處理不需要立即完成時。
QApplication::sendEvent
QApplication::sendEvent 用于立即發(fā)送事件并直接調(diào)用事件處理函數(shù)誊锭。這種方法是同步的表悬,意味著事件在函數(shù)調(diào)用時立即處理,并且控制在事件處理完畢后才返回丧靡。這種方法適用于希望立即處理事件的情況蟆沫。用于立即發(fā)送事件并直接調(diào)用事件處理函數(shù)。這種方法是同步的窘行,意味著事件在函數(shù)調(diào)用時立即處理饥追,并且控制在事件處理完畢后才返回图仓。這種方法適用于希望立即處理事件的情況罐盔。
- 立即處理事件,并且需要立即獲得處理結(jié)果救崔。
- 當(dāng)事件處理是當(dāng)前任務(wù)的關(guān)鍵部分惶看,需要在當(dāng)前上下文中完成。
- 當(dāng)確定事件處理不會導(dǎo)致遞歸或阻塞時六孵。