一、導讀
在Qt中,常見到三個exec,第一個是QApplication::exec(),第二個是QEventLoop::exec,第三個是QThread::exec()。本文從源碼角度來看看這三個exec()。
QApplication::exec()是QApplication類下的一個靜態(tài)成員函數(shù),該函數(shù)用于進入主事件循環(huán)。
QEventLoop::exec是QEventLoop類下的一個公共成員函數(shù),用于進入主事件循環(huán)。
QThread::exec()是QThread類下的一個受保護的成員函數(shù),也是用于進入事件循環(huán)。
都是進入事件循環(huán),他們之間有什么聯(lián)系呢,接著后面的分析。
二、QApplication::exec()
在實際開發(fā)中,必須調(diào)用QApplication::exec()來啟動事件處理,主事件循環(huán)會從窗口系統(tǒng)接收事件,并將這些事件分派給應用程序小部件。在調(diào)用exec()之前不能發(fā)生任何用戶交互,但是存在一種特殊的情況:QMessageBox這樣的模態(tài)小部件可以在調(diào)用exec()之前使用,因為模態(tài)小部件會調(diào)用exec()來啟動本地事件循環(huán)。
從源碼角度,QApplication::exec()會調(diào)用QGuiApplication::exec(),QGuiApplication::exec()會調(diào)用QCoreApplication::exec():
intQCoreApplication::exec()
{
//檢查exec實例
if(!QCoreApplicationPrivate::checkInstance("exec"))
return-1;
//獲取線程數(shù)據(jù)QThreadData
QThreadData*threadData=self->d_func()->threadData;
//檢查該函數(shù)的調(diào)用是否在主線程中,如果不是,則返回。
if(threadData!=QThreadData::current()){
qWarning("%s:Mustbecalledfromthemainthread",self->metaObject()->className());
return-1;
}
//檢查是否存在事件循環(huán),如果存在,則返回,否則繼續(xù)后續(xù)操作。
if(!threadData->eventLoops.isEmpty()){
qWarning("QCoreApplication:Theeventloopisalreadyrunning");
return-1;
}
threadData->quitNow=false;
//創(chuàng)建QEventLoop事件循環(huán)對象
QEventLoopeventLoop;
self->d_func()->in_exec=true;
self->d_func()->aboutToQuitEmitted=false;
//啟動事件循環(huán)
intreturnCode=eventLoop.exec();
threadData->quitNow=false;
if(self)
self->d_func()->execCleanup();
returnreturnCode;
}
從上述源碼可見,QApplication的exec()經(jīng)過層層調(diào)用,最終是使用QEventLoop實現(xiàn)事件循環(huán)。
QApplication::exec()用于啟動應用程序的事件循環(huán),讓應用程序能得以啟動運行并接收事件。
『備注,執(zhí)行應用清理的優(yōu)雅方式』:
建議將清理代碼連接到aboutToQuit()信號,而不是放在應用程序的main()函數(shù)中。這是因為,在某些平臺上,QApplication::exec()調(diào)用可能不會返回。例如,在Windows平臺上,當用戶注銷時,系統(tǒng)會在Qt關閉所有頂級窗口后終止該進程。因此,不能保證應用程序有足夠的時間退出事件循環(huán),并在QApplication::exec()調(diào)用之后,即在main()函數(shù)的末尾執(zhí)行代碼。
在QCoreApplication::exec()函數(shù)實現(xiàn)中的這幾行代碼:
if(threadData!=QThreadData::current()){
qWarning("%s:Mustbecalledfromthemainthread",self->metaObject()->className());
return-1;
}
引發(fā)出另一個有趣的知識點,那就是:在Qt多線程開發(fā)中,需要注意不要阻塞GUI線程,那么哪個是GUI線程呢?從上述源碼可以明確知道:QApplication a(argc, argv);所在線程就是GUI線程。
三、QThread::exec()
在多線程應用設計中,QThread::exec()用于為當前線程啟動一個新的事件循環(huán),為存在于該線程中的對象交付事件。在源碼中,QThread::exec()實現(xiàn)如下:
intQThread::exec()
{
Q_D(QThread);
QMutexLockerlocker(&d->mutex);
d->data->quitNow=false;
if(d->exited){
d->exited=false;
returnd->returnCode;
}
locker.unlock();
//創(chuàng)建QEventLoop事件循環(huán)。
QEventLoopeventLoop;
intreturnCode=eventLoop.exec();
locker.relock();
d->exited=false;
d->returnCode=-1;
returnreturnCode;
}
從源碼角度,也可見QThread::exec()實現(xiàn)中也調(diào)用到QEventLoop的exec()。
四、QEventLoop::exec()
QEventLoop::exec()用于進入主事件循環(huán)并等待直到exit()被調(diào)用。在調(diào)用該函數(shù)時需要傳入一個flags,如果指定了標志,則只處理標志允許的類型的事件,Qt中支持以下幾種標志:
| 序號 | 標志類型 | 描述 |
|---|---|---|
| 1 | QEventLoop::AllEvents | 所有事件 |
| 2 | QEventLoop::ExcludeUserInputEvents | 不處理用戶輸入事件,例如ButtonPress和KeyPress。 |
| 3 | QEventLoop::ExcludeSocketNotifiers | 不要處理套接字通知事件。 |
| 4 | QEventLoop::WaitForMoreEvents | 如果沒有可用的掛起事件,則等待事件。 |
注意,沒有被傳遞的事件不會被丟棄,這些事件將在下次傳入不過濾事件的標志調(diào)用procesvents()時被傳遞。
從源碼角度,QEventLoop::exec()實現(xiàn)如下:
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);
//當進入一個新的事件循環(huán)時,刪除已發(fā)布的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()本質(zhì)會調(diào)用 processEvents()分發(fā)事件。 processEvents()實現(xiàn)如下:

從上圖所示代碼中,會調(diào)用QAbstractEventDispatcher::processEvents()實現(xiàn)事件分發(fā)。QAbstractEventDispatcher類提供了一個管理Qt事件隊列的接口,它從窗口系統(tǒng)和其他地方接收事件。然后它將這些事件發(fā)送到QCoreApplication或QApplication實例進行處理和交付。
-
程序
+關注
關注
117文章
3846瀏覽量
85243 -
源碼
+關注
關注
8文章
685瀏覽量
31320 -
多線程
+關注
關注
0文章
279瀏覽量
21031 -
函數(shù)
+關注
關注
3文章
4417瀏覽量
67516 -
Qt
+關注
關注
2文章
320瀏覽量
40889
原文標題:Qt這三個exec,傻傻分不清!
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
Qt中的三個exec之間有什么聯(lián)系
評論