一、導讀
在Qt中,常見到三個exec,第一個是QApplication::exec(),第二個是QEventLoop::exec,第三個是QThread::exec()。本文從源碼角度來看看這三個exec()。
QApplication::exec()是QApplication類下的一個靜態成員函數,該函數用于進入主事件循環。
QEventLoop::exec是QEventLoop類下的一個公共成員函數,用于進入主事件循環。
QThread::exec()是QThread類下的一個受保護的成員函數,也是用于進入事件循環。
都是進入事件循環,他們之間有什么聯系呢,接著后面的分析。
二、QApplication::exec()
在實際開發中,必須調用QApplication::exec()來啟動事件處理,主事件循環會從窗口系統接收事件,并將這些事件分派給應用程序小部件。在調用exec()之前不能發生任何用戶交互,但是存在一種特殊的情況:QMessageBox這樣的模態小部件可以在調用exec()之前使用,因為模態小部件會調用exec()來啟動本地事件循環。
從源碼角度,QApplication::exec()會調用QGuiApplication::exec(),QGuiApplication::exec()會調用QCoreApplication::exec():
intQCoreApplication::exec() { //檢查exec實例 if(!QCoreApplicationPrivate::checkInstance("exec")) return-1; //獲取線程數據QThreadData QThreadData*threadData=self->d_func()->threadData; //檢查該函數的調用是否在主線程中,如果不是,則返回。 if(threadData!=QThreadData::current()){ qWarning("%s:Mustbecalledfromthemainthread",self->metaObject()->className()); return-1; } //檢查是否存在事件循環,如果存在,則返回,否則繼續后續操作。 if(!threadData->eventLoops.isEmpty()){ qWarning("QCoreApplication:Theeventloopisalreadyrunning"); return-1; } threadData->quitNow=false; //創建QEventLoop事件循環對象 QEventLoopeventLoop; self->d_func()->in_exec=true; self->d_func()->aboutToQuitEmitted=false; //啟動事件循環 intreturnCode=eventLoop.exec(); threadData->quitNow=false; if(self) self->d_func()->execCleanup(); returnreturnCode; }
從上述源碼可見,QApplication的exec()經過層層調用,最終是使用QEventLoop實現事件循環。
QApplication::exec()用于啟動應用程序的事件循環,讓應用程序能得以啟動運行并接收事件。
『備注,執行應用清理的優雅方式』:
建議將清理代碼連接到aboutToQuit()信號,而不是放在應用程序的main()函數中。這是因為,在某些平臺上,QApplication::exec()調用可能不會返回。例如,在Windows平臺上,當用戶注銷時,系統會在Qt關閉所有頂級窗口后終止該進程。因此,不能保證應用程序有足夠的時間退出事件循環,并在QApplication::exec()調用之后,即在main()函數的末尾執行代碼。
在QCoreApplication::exec()函數實現中的這幾行代碼:
if(threadData!=QThreadData::current()){ qWarning("%s:Mustbecalledfromthemainthread",self->metaObject()->className()); return-1; }
引發出另一個有趣的知識點,那就是:在Qt多線程開發中,需要注意不要阻塞GUI線程,那么哪個是GUI線程呢?從上述源碼可以明確知道:QApplication a(argc, argv);所在線程就是GUI線程。
三、QThread::exec()
在多線程應用設計中,QThread::exec()用于為當前線程啟動一個新的事件循環,為存在于該線程中的對象交付事件。在源碼中,QThread::exec()實現如下:
intQThread::exec() { Q_D(QThread); QMutexLockerlocker(&d->mutex); d->data->quitNow=false; if(d->exited){ d->exited=false; returnd->returnCode; } locker.unlock(); //創建QEventLoop事件循環。 QEventLoopeventLoop; intreturnCode=eventLoop.exec(); locker.relock(); d->exited=false; d->returnCode=-1; returnreturnCode; }
從源碼角度,也可見QThread::exec()實現中也調用到QEventLoop的exec()。
四、QEventLoop::exec()
QEventLoop::exec()用于進入主事件循環并等待直到exit()被調用。在調用該函數時需要傳入一個flags,如果指定了標志,則只處理標志允許的類型的事件,Qt中支持以下幾種標志:
序號 | 標志類型 | 描述 |
---|---|---|
1 | QEventLoop::AllEvents | 所有事件 |
2 | QEventLoop::ExcludeUserInputEvents | 不處理用戶輸入事件,例如ButtonPress和KeyPress。 |
3 | QEventLoop::ExcludeSocketNotifiers | 不要處理套接字通知事件。 |
4 | QEventLoop::WaitForMoreEvents | 如果沒有可用的掛起事件,則等待事件。 |
注意,沒有被傳遞的事件不會被丟棄,這些事件將在下次傳入不過濾事件的標志調用procesvents()時被傳遞。
從源碼角度,QEventLoop::exec()實現如下:
intQEventLoop::exec(ProcessEventsFlagsflags) { Q_D(QEventLoop); autothreadData=d->threadData.loadRelaxed(); //weneedtoprotectfromraceconditionwithQThread::exit QMutexLockerlocker(&static_cast(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex); if(threadData->quitNow) return-1; if(d->inExec){ qWarning("QEventLoop:instance%phasalreadycalledexec()",this); return-1; } structLoopReference{ QEventLoopPrivate*d; QMutexLocker&locker; boolexceptionCaught; LoopReference(QEventLoopPrivate*d,QMutexLocker&locker):d(d),locker(locker),exceptionCaught(true) { d->inExec=true; d->exit.storeRelease(false); autothreadData=d->threadData.loadRelaxed(); ++threadData->loopLevel; threadData->eventLoops.push(d->q_func()); locker.unlock(); } ~LoopReference() { if(exceptionCaught){ qWarning("Qthascaughtanexceptionthrownfromaneventhandler.Throwing " "exceptionsfromaneventhandlerisnotsupportedinQt. " "YoumustnotletanyexceptionwhatsoeverpropagatethroughQtcode. " "Ifthatisnotpossible,inQt5youmustatleastreimplement " "QCoreApplication::notify()andcatchallexceptionsthere. "); } locker.relock(); autothreadData=d->threadData.loadRelaxed(); QEventLoop*eventLoop=threadData->eventLoops.pop(); Q_ASSERT_X(eventLoop==d->q_func(),"QEventLoop::exec()","internalerror"); Q_UNUSED(eventLoop);//--releasewarning d->inExec=false; --threadData->loopLevel; } }; LoopReferenceref(d,locker); //當進入一個新的事件循環時,刪除已發布的exit事件 QCoreApplication*app=QCoreApplication::instance(); if(app&&app->thread()==thread()) QCoreApplication::removePostedEvents(app,QEvent::Quit); #ifdefQ_OS_WASM //Partialsupportfornestedeventloops:MaketheruntimethrowaJavaSrcript //exception,whichreturnscontroltothebrowserwhilepreservingtheC++stack. //Eventprocessingthencontinuesasnormal.Thesleepcallbelowneverreturns. //QTBUG-70185 if(threadData->loopLevel>1) emscripten_sleep(1); #endif while(!d->exit.loadAcquire()) processEvents(flags|WaitForMoreEvents|EventLoopExec); ref.exceptionCaught=false; returnd->returnCode.loadRelaxed(); }
從上述源碼可知,QEventLoop::exec()本質會調用 processEvents()分發事件。 processEvents()實現如下:
從上圖所示代碼中,會調用QAbstractEventDispatcher::processEvents()實現事件分發。QAbstractEventDispatcher類提供了一個管理Qt事件隊列的接口,它從窗口系統和其他地方接收事件。然后它將這些事件發送到QCoreApplication或QApplication實例進行處理和交付。
-
程序
+關注
關注
116文章
3778瀏覽量
80860 -
源碼
+關注
關注
8文章
633瀏覽量
29147 -
多線程
+關注
關注
0文章
277瀏覽量
19923 -
函數
+關注
關注
3文章
4308瀏覽量
62445 -
Qt
+關注
關注
1文章
301瀏覽量
37837
原文標題:Qt這三個exec,傻傻分不清!
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論