一、導讀
在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實例進行處理和交付。
-
程序
+關注
關注
117文章
3846瀏覽量
85238 -
源碼
+關注
關注
8文章
685瀏覽量
31319 -
多線程
+關注
關注
0文章
279瀏覽量
21027 -
函數
+關注
關注
3文章
4417瀏覽量
67502 -
Qt
+關注
關注
2文章
320瀏覽量
40876
原文標題:Qt這三個exec,傻傻分不清!
文章出處:【微信號:嵌入式小生,微信公眾號:嵌入式小生】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Qt中的三個exec之間有什么聯系
評論