在處理QT循環事件的時候遇到了問題,查了半天資料都沒弄明白問題出在哪,後來找大牛同事問了一下,同事就給我寫了QCoreApplication::processEvent()這個函數,好啦,終於搞定了,這裏小記一下,以避免之後遇到。html
因而乎這裏認真仔細的看了一下Qt的事件和事件循環。(引用了碎炎的博客)網絡
事件和事件循環架構
做爲一個事件驅動的工具包,事件和事件傳遞扮演者Qt架構中的中心角色。在本文中咱們不會給出一個對這個話題的全面的概述,咱們將着眼於一些線程相關的概念。異步
事件能被程序的內部和外部產生,舉個例子:函數
QKeyEvent和QMouseEvent對象表明了一個鍵盤和鼠標的事件,它們從窗口由用戶的操做而產生。工具
QTimerEvent對象是當某個時間被激發時投入,它們都由操做系統產生。oop
QChildEvent對象是當一個子窗口被添加或者移除時候被送入QObject的,它們的源頭是Qt程序本身。ui
事件的重點是它們被產生的時候不會被傳遞;它們會先進入事件隊列,某刻會被傳遞。傳送者本身循環訪問事件隊列並把事件傳遞給目標QObject對象,所以稱做事件循環。概念上說,時間循環就像這個:
spa
1. while (is_active)操作系統
2. {
3. while (!event_queue_is_empty)
4. dispatch_next_event();
5.
6. wait_for_more_events();
7. }
咱們經過運行QCoreApplication::exec()來進入消息循環,這個循環直到exit()或者quit()被調用時纔會被堵塞,而後退出。
這個」wait_for_more_events()」函數處於堵塞狀態,直到有新的事件被產生了。假如咱們考慮它,全部在此刻可能產生的事件是外部源頭的。所以,這個消息循環能被如下狀況喚醒:
窗口管理活動(鼠標按鍵操做等);
套接字事件;
定時器事件;
其它線程中投遞的事件、
在Unix-like系統中,窗口管理器經過套接字來通知應用程序,即便客戶端使用它們來與x server通信。若是咱們決定用內部的套接字對去實現跨線程的事件投遞,全部剩下的喚醒條件以下:
套接字;
定時器;
這個就是select(2)系統調用所作的;它監視着一系列的一系列的活動者的描述符,若是它們在必定的時間內沒有特定的活動,它們就超時了。
一個運行着的事件循環須要什麼?
這個不是完整的列表,可是若是你有總體畫面,你將可以去猜想什麼類須要一個運行着的事件循環。
Widgets的繪畫和互動:QWidget::paintEvent()將在傳遞QPaintEvent對象時候被調用,這個對象將會在調用QWidget::update()或者窗口管理器的時候產生:響應的事件將須要一個時間循環去分發。
Timers:長話短說,它們在當select(2)或者超時的時候產生,所以它們須要讓Qt在返回時間循環的時候做這些調用。
Networking「全部的底層Qt網絡類 (QTcpSocket,QUdpSocket,QTcpServer等)都是異步設計的。當你調用ready(),它們只是返回已經可用的數據,當你調 用write(),它們只是將這個操做放入隊列,適時會寫入。只有當你返回消息循環的時候真實的讀取,寫入纔會執行。注意它們確實提供了同步的方法,可是 它們的用法是不被提倡的,由於它們會堵塞事件循環。高級類,好比QNetworkAccessManager,簡單的不提供同步API,須要一個事件循 環。
堵塞事件循環
在咱們討論爲何你應該從不堵塞消息循環以前,咱們試着分析堵塞的含義。假象你有一個按鈕,它將會在它被點擊的時候發出clicked信號;在咱們的對象中鏈接着一個槽函數,當你點擊了那個按鈕後,棧追蹤將會像這樣:
1. main(int, char **)
2. QApplication::exec()
3. […]
4. QWidget::event(QEvent *)
5. Button::mousePressEvent(QMouseEvent *)
6. Button::clicked()
7. […]
8. Worker::doWork()
在main函數中咱們啓動了時間循環,日常的調用了 exex(),窗口管理器給咱們發送了一個鼠標點擊事件,它被Qt內核取走,轉換成QMouseEvent並被送往咱們widget的event()方 法,該方法被QApplication::notify()發送。由於按鈕沒有重寫event(),基類方法將被調用,QWidget::event() 檢測到了這個事件確實是一個鼠標點擊事件,而後調用特定的事件處理函數,那就是Button::mousePressEvent(),咱們重寫這個方法去 發送clicked()信號,這將會調用被鏈接的槽函數。
當該對象處理量很大,那麼消息循環在做什麼?咱們應該猜想它:什麼都不作!它分發鼠標按下事件,而後就堵塞着等待着事件處理函數返回。這個就是堵塞了時間循環,它意味着沒有消息被分發了,知道咱們從槽函數返回了,而後繼續處理掛起的消息。
在消息循環被卡住的狀況下,widgets將不能更新它們 自身,不可能有更多的互動,timers將不會被激發,網絡通信將緩慢下來,或者中止。進一步的說,許多窗口管理器將檢測到你的應用程序不在處理事件了, 而後告訴用戶你的程序沒有響應。這就是爲何快速的對事件響應而且即時返回到事件循環是多麼的重要!
強制事件分發
因此,假如咱們有一個很長的任務去運行可是又不但願堵塞這 個消息循環,該怎麼作呢?一個可能的回答是將這個任務移到另外一個線程中,在下一個張潔咱們將看到這是如何作的。咱們也能手動強制事件循環去運行,這個方法 是經過在堵塞的任務函數中調用QCoreApplication::processEvent()來實現 的,QCoreApplication::processEvent()將處理全部在消息隊列中的消息並返回給調用者。
另外一個可選的選項是咱們可以強制重入事件循環的對象,就是QEventLoop類。經過調用QEventLoop::exec()咱們將重入事件循環,而後咱們能將槽函數QVentLoop::quit()鏈接到信號上去使它退出。舉個例子:
1. QNetworkAccessManager qnam;
2. QNetworkReply *reply = qnam.get(QNetworkRequest(QUrl(...)));
3. QEventLoop loop;
4. QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
5. loop.exec();
6. /* reply has finished, use it */
QNetworkReply不提供堵塞的API,它要求一個在運行的事件循環。咱們進入了一個本地的事件循環,而後當回覆完成時候,這個本地的循環退出了。
要特別當心的是在其它路徑下重入事件循環:它可能致使不但願的遞歸!讓咱們回到前面看看按鈕的例子。假如咱們在槽函數中調用了QCoreApplication::processEvent(),當用戶點擊了這個按鈕,這個槽函數將被再次調用:
1. main(int, char **)
2. QApplication::exec()
3. […]
4. QWidget::event(QEvent *)
5. Button::mousePressEvent(QMouseEvent *)
6. Button::clicked()
7. […]
8. Worker::doWork() // first, inner invocation
9. QCoreApplication::processEvents() // we manually dispatch events and…
10. […]
11. QWidget::event(QEvent * ) // another mouse click is sent to the Button…
12. Button::mousePressEvent(QMouseEvent *)
13. Button::clicked() // which emits clicked() again…
14. […]
15. Worker::doWork() // DANG! we’ve recursed into our slot.
一個快速並簡便的變通方法是把QEventLoop::ExcludeUserInputEvent傳遞給QCoreApplication::processEvents(),這會告訴消息循環不要再次分發任何用戶的輸入事件。
幸運的是,這個相同的事情不會在檢測事件中發生。事實上,它們被Qt經過特殊的方法處理了,只有當運行的時間循環有了一個比deleteLater被調用後更小的」nesting」值纔會被處理:
將不會使object成爲一個懸空指針。相同的東西被應用 到了本地的事件循環中。惟一的一個顯著區別我已經發現了,它在假如當沒有事件循環在運行的時候deleteLater被調用了的條件下,而後第一個消息循 環進入了後會取走這個事件,而後刪除這個object。這是至關合理的,由於Qt不知道任何外部的循環將最終影響這個檢測,所以立刻刪除了這個 object。