Qt事件循環與狀態機事件循環的思考

寫下這個給本身備忘,關於事件循環以及多線程方面的東西我還須要多多學習。首先咱們都知道程序有一個主線程,在GUI程序中這個主線程也叫GUI線程,圖形和繪圖相關的函數都是由主線程來提供。主線程有個事件循環Event Loop,其實就是一個死循環在不斷的等待你的消息隊列,經過消息隊列完成響應用戶操做,繪圖,以及相關操做。咱們都知道QDialog有一個exec函數,這個函數會造成「模態」對話框,而後等待用戶去輸入OK仍是Cancel,不然他毫不返回,以下多線程

void test()
{
    QDialog dialog;
    dialog.exec();

    qDebug() << "finish exec";
}

咱們能夠看這個簡單的例子,能夠看到,當dialog被exec以後,咱們的qDebug是不會輸出的,除非咱們人爲去點了對話框的OK,不然,就會一直卡在exec之上。這個時候可能同窗會有我一開始同樣的誤解,咱們會誤覺得此時事件循環中止了,其實並非中止,而是阻塞住了。爲何會阻塞?由於test這個函數沒有返回嘛!異步

m_stateManager->postEvent(ev);

投遞事件官方API也說的很清楚,會當即返回,因此別去擔憂此時投遞的事件進入不到消息隊列,真正要關心的是此時dialog的exec讓你的主線程阻塞了,這個時候消息隊列上的事件都不會進行操做,都在等待dialog的返回,只有dialog返回,接下來的事件纔會依次進行。要記住,消息是能夠正常投遞的函數

那麼,有沒有辦法可讓dialog.exec()當即返回,同時個人對話框還在呢?方法是有的oop

void test()
{
    metaObject()->invokeMethod(this, "invokeTest", Qt::QueuedConnection);

    qDebug() << "finish exec";
}

Q_INVOKABLE void invokeTest()
{
    QDialog dialog;
    dialog.exec();
}

答案就是用Qt提供的元對象系統Meta Object System的invokeMethod,而且將第三個參數設爲Qt:QueuedConnection。從字面上咱們也能夠看的出來,這個調用不會去當即調用,相反他是異步的,他會把這個函數投遞到事件隊列裏,也就是說,這個例子中的qDebug會輸出,輸出以後,事件隊列纔會去調用dialog.exec()這個函數,固然了,調用這個函數以後又會達到咱們一開始的那個阻塞的效果,你經過異步到最後的觸發,始終你須要去面對exec給你當前事件循環形成阻塞的問題post

讓咱們再深刻一點,當一個事件循環的時候還算簡單。可是咱們知道,Qt中對於狀態機他是有一個異步的事件循環的,也就是說外面有事件循環,狀態機自己也有事件循環。好比學習

m_stateManager->postEvent(ev);

m_tool->run();

你給狀態機投遞了一個事件,他根據狀態遷移去調用你的tool,這一切看起來很美好,但若是你此時的tool是個跟以前同樣的阻塞的exec呢?讓咱們來看一下。this

void Tool::run()
{
    QDialog dialog;
    dialog.exec();
}

對於這個狀況,當咱們運行tool以後,咱們的狀態機就跟以前的主事件事件循環同樣被阻塞了,也就是說若是我此時繼續spa

m_stateManager->postEvent(ev2);

和以前同樣,這個postEvent會當即返回,由於投遞到事件隊列都是當即返回的。可是關鍵的問題在於你的狀態機整個事件循環都中止不動了,都在等你以前的tool運行結束,但由於你以前的tool是個dialog.exec()你必須手動去點OK,否則你的狀態機事件循環就阻塞不動了,這個時候若是你的客戶不斷的去點你這個tool的event,那會產生噩夢般的效果----你點完OK以後又會來OK以後又會來OK。。。這其實就是你一旦點了OK,你的消息隊列就又能夠循環了,以前等待的ev就都會去執行了。並且要注意的就是,此時你的exec的執行在主線程上,只是不能進行返回,但仍是能夠接收諸如鍵盤,鼠標等事件投遞。線程

前面也說了,事件循環和狀態機循環是兩個獨立的循環,其實這也很好理解。若是沒有事件循環,狀態機事件怎麼知道你有沒有按下這個鍵?從而去投遞給狀態機呢?其實也就是說當你狀態機事件阻塞的時候,你的主事件循環還在不斷的接收你的鍵盤和鼠標的操做,這一點是沒有影響的。code

所以,要想實如今tool的時候我還能相應別的狀態事件,其實作法也是同樣的,就是

void Tool::run()
{
    metaObject()->invokeMethod(this, "invokeTest", Qt::QueuedConnection);
}

Q_INVOKABLE void invokeTest()
{
    QDialog dialog;
    dialog.exec();
}

當即返回,這個」當即返回「並非說你的事情作完了,而是你更想讓狀態機可以進行以後事件的循環,別去由於你的dialog而耽誤了你們。

最後說說模態這個主題,其實模態的理解就是你的消息隊列都在正常進行,由於你不斷的在等待,致使事件循環不能進行下去,必須你這邊正常返回,你接下來的操做才能繼續。


今天又從新思考了一下這個問題。同步的意思彷佛就是必需要執行完成才能返回。異步的概念就是當即返回,以後執行,會把他扔到消息隊列裏,待同步函數處理完以後,而後去搜索事件隊列進行操做。其實狀態機歸根結底就是一堆信號連接,只是他的方向是規矩狀態遷移表來進行。做爲主線程的Event Loop來講,當dialog進入exec的時候,就是就是在進行事件隊列,並非說他此時把事件隊列給阻塞了,這個我以前理解有問題。exec的含義就是去處理事件隊列,去處理事件循環,去檢索當前還有哪些事件能夠被處理,從而去正常處理。好比咱們有一個主程序窗口MainWindow,有一個Dialog,此時咱們去調用dialog的exec,內部會去建立一個QEventLoop,又由於這個dialog的所在線程和MainWindow在同一個線程上,因此看上去彷佛是兩個EventLoop,但實際上都是同一個線程的Event Loop(一個線程只能有一個Event Loop,這是原子性問題)。因此在dialog進行exec的時候他會去檢索主線程上的事件隊列去操做。

而咱們以前講的狀態機,其實仔細想了想很簡單,你就把他理解成是主程序總的Event Loop中的一個事件,他在進行操做的時候,不返回(tool去調用dialog的exec看上去彷佛進行了事件循環在等待你新的event,但別忘了,你自己這個tool的run就是經過事件隊列去觸發的)因此必需要等待這個tool的exec返回,你的事件隊列才能正常下去。

再次強調:

  1. exec並非說事件隊列被你阻塞,而是纔是讓你進入一個真正等待處理事件隊列的過程。
  2. 同一個線程只能有一個Event Loop,這個能夠參考CP單核心單線程的處理邏輯。
  3. 在進行事件隊列進行事件操做的時候,其實內部就是同步的方式在進行,必須等待函數所有執行完畢才能真正返回才能真正進行以後的event,這也能夠說的通咱們以前舉的狀態機的例子,看上去這個狀態機引起了咱們的tool,tool中調用了dialog的exec,看上去彷佛很美好,在等待狀態事件了。但此時你這個exec不返回,你如何讓事件Event Loop繼續進行下去。
  4. 同步函數就是必需要等待函數操做完成以後才能返回的函數。異步函數就是直接返回,他具體何時進行操做,待具體實現查看。(也多是本線程的事件隊列,也多是別的線程進行run)。若是是別的線程進行run的時候,你可能會去想這個當即返回的問題,其實很簡單,Qt的run都是start,其實就跟postEvent同樣,只是簡單的把他註冊給線程管理器,由線程管理器再去跑他的run函數,那你本地的start固然當即返回了。
相關文章
相關標籤/搜索