QT核心-事件循环
事件循環(huán)
接著上一章沒(méi)說(shuō)完的繼續(xù),事件循環(huán)的源碼其實(shí)不太好刨,它是和平臺(tái)相關(guān)的,比如Windows上它用了PostMessage、PeekMessage和WaitForSingleObject等一系列Windows API完成的,當(dāng)然線程也是調(diào)用平臺(tái)API,所以這里不過(guò)多關(guān)注其源碼,而是更多的理解我們能用到的功能。
上一章總結(jié)其實(shí)就一句話: 生存線程就是QObject的event方法的執(zhí)行線程。通過(guò)方法名我們就能知道這個(gè)方法是和事件相關(guān)的,它并不會(huì)被線程直接調(diào)用,而是由QEventLoop內(nèi)部去調(diào)用,我們看一下QThread的run方法的默認(rèn)實(shí)現(xiàn)和與之相關(guān)的exec方法:
所以說(shuō)如果直接只用QThread::run的默認(rèn)實(shí)現(xiàn)或者重寫run方法之后手動(dòng)調(diào)用過(guò)exec的話整個(gè)線程將由QEventLoop接管。
QEventLoop
- 先說(shuō)一下QEventLoop的用法,正如上面QThread::exec中的代碼,開啟一個(gè)新的事件循環(huán)只需要兩行代碼:
當(dāng)程序執(zhí)行到int returnCode = eventLoop.exec();時(shí),會(huì)卡在exec函數(shù)里,這里面其實(shí)是個(gè)死循環(huán),它會(huì)提取當(dāng)前線程的事件隊(duì)列中的事件并分配給相應(yīng)對(duì)象執(zhí)行(由QCoreApplication::postEvent發(fā)送),所以它不會(huì)影響其他事件繼續(xù)執(zhí)行,僅僅只是當(dāng)前流程會(huì)停下而已,退出該事件循環(huán)為調(diào)用eventLoop.quit(),注意看下面例子。那么它和while(true){}這種形式的區(qū)別就很明顯了,while(true)是直接將當(dāng)前線程卡死在這,什么都不執(zhí)行。下面舉個(gè)例子區(qū)分這兩個(gè):
EventLoopTest.h
EventLoopTest.cpp
#include "EventLoopTest.h"#include <QCoreApplication> #include <QDateTime> #include <QDebug> #include <QEvent> #include <QEventLoop> #include <QTimer>EventLoopTest::EventLoopTest(QObject *parent) : QObject(parent) {}void EventLoopTest::test() {qDebug() << "into test:" << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);QCoreApplication::postEvent(this, new QEvent(static_cast<QEvent::Type>(QEvent::User)));auto start = QDateTime::currentMSecsSinceEpoch();while (QDateTime::currentMSecsSinceEpoch() - start < 1000) {}qDebug() << "out test:" << QDateTime::currentDateTime().toString(Qt::ISODateWithMs); }bool EventLoopTest::event(QEvent *event) {if (event->type() == QEvent::User) {qDebug() << "into event:" << QDateTime::currentDateTime().toString(Qt::ISODateWithMs);}return QObject::event(event); }main.cpp
#include "MainWindow.h"#include <QApplication> #include <QTimer>#include "EventLoopTest.h"int main(int argc, char *argv[]) {QApplication a(argc, argv);MainWindow w;w.show();//! 延遲100毫秒再構(gòu)造EventLoopTest并執(zhí)行其test方法//! 主要目的是等待a.exec()方法執(zhí)行,它會(huì)開啟一個(gè)最根本的事件循環(huán)//! 當(dāng)然不延遲直接構(gòu)造執(zhí)行也可以//! 這里要注意,這個(gè)lambda表達(dá)式是a.exec()內(nèi)部的事件循環(huán)調(diào)用的(暫時(shí)不理解也不影響)QTimer::singleShot(100, [&w]() {auto e = new EventLoopTest(&w);e->test();});return a.exec(); }輸出結(jié)果
into test: "2021-03-14T15:39:52.738" out test: "2021-03-14T15:39:53.739" into event: "2021-03-14T15:39:53.739"根據(jù)上面的輸出可以明顯看到,while(true)的暫停程序流程方式一定是等到test結(jié)束之后再執(zhí)行的event方法。該event方式是由main函數(shù)中的a.exec();里面的事件循環(huán)調(diào)用的。
2. QEventLoop方式:
其他代碼完全不變,將test方法中的代碼換成下面
輸出結(jié)果
into test: "2021-03-14T15:54:11.513" into event: "2021-03-14T15:54:11.514" out test: "2021-03-14T15:54:12.515"由上面結(jié)果可以看到,即使test方法還沒(méi)有退出,event方法也能被正確執(zhí)行,這就是QEventLoop的作用。
- 下面舉一個(gè)平常開始時(shí)經(jīng)常用到的例子
注意: 使用這個(gè)例子時(shí)可能會(huì)出現(xiàn)由https引發(fā)的openssl庫(kù)的問(wèn)題,請(qǐng)先自行百度解決一下。
NetworkTest.h
NetworkTest.cpp
#include "NetworkTest.h"#include <QEventLoop> #include <QNetworkAccessManager> #include <QNetworkReply> #include <QNetworkRequest> #include <QTimer>NetworkTest::NetworkTest(QObject *parent) : QObject(parent) {}void NetworkTest::req() {QNetworkAccessManager man;QNetworkRequest req;req.setUrl(QUrl("https://www.baidu.com/"));auto reply = man.get(req);QEventLoop l;QTimer::singleShot(1000, &l, &QEventLoop::quit);l.exec();qDebug() << "read:" << reply->readAll();reply->deleteLater(); }main.cpp
#include "MainWindow.h"#include <QApplication> #include <QTimer>#include "NetworkTest.h"int main(int argc, char *argv[]) {QApplication a(argc, argv);MainWindow w;w.show();//! 延遲100毫秒再構(gòu)造NetworkTest并執(zhí)行其req方法//! 主要目的是避免迷惑,等待a.exec()方法執(zhí)行并由其內(nèi)部開啟一個(gè)默認(rèn)事件循環(huán)//! 這里直接構(gòu)造并執(zhí)行也沒(méi)問(wèn)題,這里這樣寫主要是給一些愛深想的人提供一個(gè)例子,不用太過(guò)糾結(jié)QTimer::singleShot(100, [&w]() {auto n = new NetworkTest(&w);n->req();});return a.exec(); }輸出結(jié)果
read: "<html>\r\n<head>\r\n\t<script>\r\n\t\tlocation.replace(location.href.replace(\"https://\",\"http://\"));\r\n\t</script>\r\n</head>\r\n<body>\r\n\t<noscript><meta http-equiv=\"refresh\" content=\"0;url=http://www.baidu.com/\"></noscript>\r\n</body>\r\n</html>"當(dāng)然,這里的數(shù)據(jù)可能不全,但無(wú)關(guān)緊要,這里只是演示,我們主要是想以同步的方式接收網(wǎng)絡(luò)請(qǐng)求的數(shù)據(jù)。正常用法是將QTimer::singleShot(1000, &l, &QEventLoop::quit);這行代碼換成connect(reply, &QNetworkReply::finished, &l, &QEventLoop::quit);
上面req方法里面換成while(true)形式是永遠(yuǎn)沒(méi)辦法讀取到數(shù)據(jù)的
輸出結(jié)果永遠(yuǎn)是read: ""
這里可以大膽猜測(cè),QNetworkReply內(nèi)部從socket讀取數(shù)據(jù)是依賴事件循環(huán)的,如上面使用QEventLoop開啟一個(gè)子事件循環(huán),QNetworkReply中的數(shù)據(jù)讀取方法仍能正常執(zhí)行。而使用while(true)的方式時(shí),當(dāng)前線程永遠(yuǎn)被卡在這個(gè)死循環(huán)里,QNetworkReply無(wú)法正常讀取數(shù)據(jù)。
總結(jié)
- 上一篇: 发明专利、实用新型专利——下载步骤
- 下一篇: Tensorflow C++使用ops: