跨平臺開發(fā)框架Qt最新資訊:Qt6中的異步API
Qt(發(fā)音為“ cute”,而不是“ cu-tee”)是一個跨平臺框架,通常用作圖形工具包,它不僅創(chuàng)建CLI應(yīng)用程序中非常有用。而且它也可以在三種主要的臺式機操作系統(tǒng)以及移動操作系統(tǒng)(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式設(shè)備,Android(Necessitas)和iOS的端口上運行?,F(xiàn)在我們?yōu)槟闾峁┝嗣赓M的試用版。趕快點擊下載Qt最新試用版吧>>
大家可能知道Qt提供了幾種多線程結(jié)構(gòu)(線程,互斥體,等待條件等),以及更高級別的API,如QThreadPoolQt Concurrent和其他相關(guān)類。在本文中,我們將專注于更高級別的異步API和Qt 6中引入的更改。
Qt中更高級別的并發(fā)API
Qt Concurrent通過消除對低級同步(基元,例如互斥鎖和鎖)的需求,并手動管理多個線程,使多線程編程變得更加容易。它為并行處理可迭代容器提供了映射,過濾和歸約算法(從功能編程中可以更好地了解)。此外,還有類QFuture,QFutureWatcher和,QFutureSynchronizer用于訪問和監(jiān)視異步計算的結(jié)果。盡管所有這些都非常有用,但是仍然存在一些缺點,例如無法使用QFuture 在Qt Concurrent之外,缺乏對鏈接多個計算以簡化和簡潔代碼的支持,缺乏Qt Concurrent API的靈活性等。對于Qt 6,目前正在嘗試解決這些問題,并使Qt的多線程編程更加有趣 !
將延續(xù)附加到QFuture多線程編程中的一種常見情況是運行異步計算,這又需要調(diào)用另一個異步計算并將數(shù)據(jù)傳遞給該異步計算,該異步計算依賴于另一個計算,依此類推。由于每個階段都需要上一個階段的結(jié)果,因此您需要等待(通過阻止或輪詢)直到上一個階段完成并使用其結(jié)果,或者以“回調(diào)”的方式構(gòu)造代碼。這些選項都不是完美的:要么浪費資源等待時間,要么獲取復(fù)雜的無法維護的代碼。添加新的階段或邏輯(用于錯誤處理等)會進一步增加復(fù)雜性。
為了更好地理解問題,讓我們考慮以下示例。假設(shè)我們要從網(wǎng)絡(luò)下載大圖像,對其進行一些繁重的處理,然后在我們的應(yīng)用程序中顯示生成的圖像。因此,我們執(zhí)行以下步驟:
- 發(fā)出網(wǎng)絡(luò)請求并等待,直到收到所有數(shù)據(jù)
- 根據(jù)原始數(shù)據(jù)創(chuàng)建圖像
- 處理圖像
- 展示下
對于每個需要依次調(diào)用的步驟,我們都有以下方法:
QByteArray download(const QUrl &url); QImage createImage(const QByteArray &data); QImage processImage(const QImage &image); void show(const QImage &image);
我們可以使用QtConcurrent異步運行這些任務(wù)并QFutureWatcher監(jiān)視進度:
void loadImage(const QUrl &url) { QFuture data = QtConcurrent::run(download, url); QFutureWatcher dataWatcher; dataWatcher.setFuture(data); connect(&dataWatcher, &QFutureWatcher ::finished, this, [=] { // handle possible errors // ... QImage image = createImage(data); // Process the image // ... QFuture processedImage = QtConcurrent::run(processImage, image); QFutureWatcher<QImage> imageWatcher; imageWatcher.setFuture(processedImage); connect(&imageWatcher, &QFutureWatcher::finished, this, [=] { // handle possible errors // ... show(processedImage); }); }); }
我們要添加到鏈中的步驟越多越難看。QFuture通過添加對通過QFuture::then()方法附加延續(xù)的支持,可以幫助解決此問題:
auto future = QtConcurrent::run(download, url) .then(createImage) .then(processImage) .then(show);
這無疑看起來要好得多!但是缺少一件事:錯誤處理。您可以執(zhí)行以下操作:
auto future = QtConcurrent::run(download, url) .then([](QByteArray data) { // handle possible errors from the previous step // ... return createImage(data); }) .then(...) ...
這將起作用,但是錯誤處理代碼仍與程序邏輯混合在一起。另外,如果其中一個步驟失敗,我們可能也不想運行整個鏈。這可以通過使用QFuture::onFailed()方法來解決,該方法允許我們?yōu)槊糠N可能的錯誤類型附加特定的錯誤處理程序:
auto future = QtConcurrent::run(download, url) .then(createImage) .then(processImage) .then(show) .onFailed([](QNetworkReply::NetworkError) { // handle network errors }) .onFailed([](ImageProcessingError) { // handle image processing errors }) .onFailed([] { // handle any other error });
請注意,使用.onFailed()需要啟用異常類。如果任何步驟失敗并發(fā)生異常,則鏈會中斷,并調(diào)用與拋出的異常類型匹配的錯誤處理程序。
根據(jù)信號創(chuàng)建QFuture給定一個帶有signal 的QObject基于類,您可以通過以下方式將此用作Future類:MyObjectvoid mySignal(int)
QFuture intFuture = QtFuture::connect(&object, &MyObject::mySignal);
現(xiàn)在,您可以將延續(xù),失敗或取消處理程序附加到最終的結(jié)果上。
請注意,最終結(jié)果的類型與signal的自變量類型匹配。如果沒有參數(shù),則 返回 QFuture<void>。如果有多個參數(shù),則結(jié)果存儲在中std::tuple。
讓我們回到圖像處理示例的第一步(即下載),以了解這在實踐中如何有用。有很多方法可以實現(xiàn)它,我們將使用QNetworkAccessManager來發(fā)送網(wǎng)絡(luò)請求并獲取數(shù)據(jù):
QNetworkAccessManager manager; ... QByteArray download(const QUrl &url) { QNetworkReply *reply = manager.get(QNetworkRequest(url)); QObject::connect(reply, &QNetworkReply::finished, [reply] {...}); // wait until we've received all data // ... return data; }
但是上面的阻塞等待不是很好,如果我們可以避開它那就更好了,比如說“當(dāng)QNetworkAccessManager獲取數(shù)據(jù)時,創(chuàng)建一個圖像,然后對其進行處理然后顯示”。我們可以通過將網(wǎng)絡(luò)訪問管理器的finished()信號連接到QFuture:
QNetworkReply *reply = manager.get(QNetworkRequest(url)); auto future = QtFuture::connect(reply, &QNetworkReply::finished) .then([reply] { return reply->readAll(); }) .then(QtFuture::Launch::Async, createImage) .then(processImage) .then(show) ...
您會注意到,現(xiàn)在我們不再使用QtConcurrent::run()異步下載而是在新線程中返回數(shù)據(jù),我們只是連接到QNetworkAccessManager::finished()信號,從而開始了計算鏈。還請注意以下行中的其他參數(shù):
.then(QtFuture::Launch::Async, createImage)
默認(rèn)情況下.then()在父進程運行所在的同一線程(在本例中為主線程)中調(diào)用by附加的延續(xù)。現(xiàn)在,我們不再使用QtConcurrent::run()異步啟動鏈,我們需要傳遞附加QtFuture::Launch::Async參數(shù),以在單獨的線程中啟動連續(xù)鏈,并避免阻塞UI。
創(chuàng)建一個QFuture到目前為止,在QFuture內(nèi)部創(chuàng)建和存儲值的唯一“官方”方法是QtConcurrent中的一種方法。所以QtConcurrent以外,QFuture不是很有用。在Qt 6中,將Andrei Golubev引入了“Setter”, QFuture: QPromise的對應(yīng)物。它可用于為異步計算設(shè)置值,進度和異常,以后可通過訪問QFuture。為了演示其工作原理,讓我們再次重寫圖像處理示例,并使用QPromise該類:
QFuture download(const QUrl &url) { QPromise promise; QFuture future = promise.future(); promise.reportStarted(); // notify that download is started QNetworkReply *reply = manager.get(QNetworkRequest(url)); QObject::connect(reply, &QNetworkReply::finished, [reply, p = std::move(promise)] { p.addResult(reply->readAll()); p.reportFinished(); // notify that download is finished reply->deleteLater(); }); return future; }auto future = download() .then(QtFuture::Launch::Async, createImage) .then(processImage) .then(show) ...
QtConcurrent的變化
-現(xiàn)在,您可以為QtConcurrent的所有方法設(shè)置自定義線程池,而不是始終在全局線程池上運行它們并可能阻止其他任務(wù)的執(zhí)行。
-映射和過濾器縮小算法現(xiàn)在可以采用初始值,因此您不必為沒有默認(rèn)構(gòu)造函數(shù)的類型做變通辦法。
- QtConcurrent::run進行了改進,可以處理可變數(shù)量的參數(shù)和僅移動類型。
此外,我們在QtConcurrent中添加了兩個新的API,以為用戶提供更大的靈活性。讓我們更詳細(xì)地看一下。
QtConcurrent :: runWithPromise
QtConcurrent::runWithPromise()Jarek Kobus開發(fā)的新方法是QtConcurrent框架的另一個不錯的補充。它非常類似于QtConcurrent::run(),不同之處在于,它使QPromise與給定任務(wù)相關(guān)聯(lián)的對象可供用戶訪問。
auto future = QtConcurrent::runWithPromise( [] (QPromise &promise, /* other arguments may follow */ ...) { // ... for (auto value : listOfValues) { if (promise.isCanceled()) // handle the cancellation // do some processing... promise.addResult(...); promise.setProgressValue(...); } }, /* pass other arguments */ ...);
runWithPromise()用戶可以更好地控制任務(wù),并且可以響應(yīng)取消或暫停請求,進行進度報告等操作,而這些使用QtConcurrent::run()是不可能實現(xiàn)的。
QtConcurrent ::任務(wù)
QtConcurrent::task()提供了一個流暢的界面,用于在單獨的線程中運行任務(wù)。它對于QtConcurrent::run()是更為現(xiàn)代的替代方案,并配置任務(wù)的方式也更為方便。您可以使用任何順序指定參數(shù),跳過不需要的參數(shù),等等,而不是使用少數(shù)幾個參數(shù)之一來傳遞參數(shù)來運行任務(wù)。例如:
QFuture future = QtConcurrent::task(doSomething) .withArguments(1, 2, 3) .onThreadPool(pool) .withPriority(10) .spawn();
請注意,與run()不同,您還可以為任務(wù)傳遞優(yōu)先級。
本篇文章中的內(nèi)容你都學(xué)會了嗎?如果這篇文章沒能滿足你的需求、點擊獲取更多文章教程!現(xiàn)在立刻下載Qt免費試用吧!更多Qt類開發(fā)工具QtitanRibbon、QtitanChart、QtitanNavigation、QtitanDocking、QtitanDataGrid在線訂購現(xiàn)直降1000元,歡迎咨詢慧都在線客服獲取更多優(yōu)惠>>