Qt之QEventLoop淺探

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)致遞歸或阻塞時六孵。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纬黎,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子劫窒,更是在濱河造成了極大的恐慌本今,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件主巍,死亡現(xiàn)場離奇詭異冠息,居然都是意外死亡,警方通過查閱死者的電腦和手機孕索,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進店門逛艰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人搞旭,你說我怎么就攤上這事散怖」矫啵” “怎么了?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵镇眷,是天一觀的道長咬最。 經(jīng)常有香客問我,道長欠动,這世上最難降的妖魔是什么丹诀? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮翁垂,結(jié)果婚禮上铆遭,老公的妹妹穿的比我還像新娘。我一直安慰自己沿猜,他們只是感情好枚荣,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啼肩,像睡著了一般橄妆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上祈坠,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天害碾,我揣著相機與錄音,去河邊找鬼赦拘。 笑死慌随,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的躺同。 我是一名探鬼主播阁猜,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼蹋艺!你這毒婦竟也來了剃袍?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤捎谨,失蹤者是張志新(化名)和其女友劉穎民效,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涛救,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡畏邢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了州叠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片棵红。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖咧栗,靈堂內(nèi)的尸體忽然破棺而出逆甜,到底是詐尸還是另有隱情虱肄,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布交煞,位于F島的核電站咏窿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏素征。R本人自食惡果不足惜集嵌,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望御毅。 院中可真熱鬧根欧,春花似錦、人聲如沸端蛆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽今豆。三九已至嫌拣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呆躲,已是汗流浹背异逐。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留插掂,地道東北人灰瞻。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓,卻偏偏與公主長得像燥筷,于是被迫代替她去往敵國和親箩祥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容

  • Qt 事件和信號的關(guān)系 Qt的事件是windows的底層消息封裝而成的肆氓。這個消息和MFC里的消息是同一概念,都是指...
    行走的代碼閱讀 1,498評論 0 0
  • 相同點 目的都是為了解決對象間通信的問題。 事件發(fā)生或信號發(fā)射時會有相應(yīng)的處理函數(shù)捐凭。 不同之處 返回值事件有返回值...
    riverlet閱讀 1,930評論 0 0
  • Qt筆記總結(jié) 作者:hackett 微信公眾號:加班猿 一拨扶、常用控件 按鈕類 QPushButton QtoolB...
    加班猿閱讀 559評論 0 1
  • 簡介 這里簡單介紹Qt的一些核心機制,具體參見Qt文檔茁肠。 主要包含內(nèi)容: Qt的信號和槽患民,以及事件機制 Qt Ob...
    QuietHeart閱讀 1,494評論 0 3
  • QT學(xué)習(xí)筆記 1.對象樹:在Qt中,每個 QObject 內(nèi)部都有一個list垦梆,用來保存所有的 children匹颤,...
    油炸花生米1閱讀 651評論 0 1