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