Qt

(發音為“ cute”,而不是“ cu-tee”)是一個跨平臺框架,通常用作圖形工具包,它不僅建立CLI應用程式中非常有用。而且它也可以在三種主要的桌上型電腦作業系統以及移動作業系統(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式裝置,Android(Necessitas)和iOS的埠上執行。

如果本篇文章不能滿足你的需求點選獲取更多文章教程

大家可能知道Qt提供了幾種多執行緒結構(執行緒,互斥體,等待條件等),以及更高級別的API,如QThreadPoolQt Concurrent和其他相關類。在本文中,我們將專注於更高級別的非同步API和Qt 6中引入的更改。

Qt中更高級別的併發API

Qt Concurrent透過消除對低階同步(基元,例如互斥鎖和鎖)的需求,並手動管理多個執行緒,使多執行緒程式設計變得更加容易。它為並行處理可迭代容器提供了對映,過濾和歸約演算法(從功能程式設計中可以更好地瞭解)。此外,還有類QFuture,QFutureWatcher和,QFutureSynchronizer用於訪問和監視非同步計算的結果。儘管所有這些都非常有用,但是仍然存在一些缺點,例如無法使用QFuture 在Qt Concurrent之外,缺乏對連結多個計算以簡化和簡潔程式碼的支援,缺乏Qt Concurrent API的靈活性等。對於Qt 6,目前正在嘗試解決這些問題,並使Qt的多執行緒程式設計更加有趣 !

將延續附加到QFuture

多執行緒程式設計中的一種常見情況是執行非同步計算,這又需要呼叫另一個非同步計算並將資料傳遞給該非同步計算,該非同步計算依賴於另一個計算,依此類推。由於每個階段都需要上一個階段的結果,因此您需要等待(透過阻止或輪詢)直到上一個階段完成並使用其結果,或者以“回撥”的方式構造程式碼。這些選項都不是完美的:要麼浪費資源等待時間,要麼獲取複雜的無法維護的程式碼。新增新的階段或邏輯(用於錯誤處理等)會進一步增加複雜性。

為了更好地理解問題,讓我們考慮以下示例。假設我們要從網路下載大影象,對其進行一些繁重的處理,然後在我們的應用程式中顯示生成的影象。因此,我們執行以下步驟:

發出網路請求並等待,直到收到所有資料

根據原始資料建立影象

處理影象

展示下

對於每個需要依次呼叫的步驟,我們都有以下方法:

QByteArray download(const QUrl &url);

QImage createImage(const QByteArray &data);

QImage processImage(const QImage &image);

void show(const QImage &image);

我們可以使用QtConcurrent非同步執行這些任務並QFutureWatcher監視進度:

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 imageWatcher;

imageWatcher。setFuture(processedImage);

connect(&imageWatcher, &QFutureWatcher::finished, this, [=] {

// handle possible errors

// 。。。

show(processedImage);

});

});

}

我們要新增到鏈中的步驟越多越難看。QFuture透過新增對透過QFuture::then()方法附加延續的支援,可以幫助解決此問題:

auto future = QtConcurrent::run(download, url)

。then(createImage)

。then(processImage)

。then(show);

這無疑看起來要好得多!但是缺少一件事:錯誤處理。您可以執行以下操作:

auto future = QtConcurrent::run(download, url)

。then([](QByteArray data) {

// handle possible errors from the previous step

// 。。。

return createImage(data);

})

。then(。。。)

。。。

這將起作用,但是錯誤處理程式碼仍與程式邏輯混合在一起。另外,如果其中一個步驟失敗,我們可能也不想執行整個鏈。這可以透過使用QFuture::onFailed()方法來解決,該方法允許我們為每種可能的錯誤型別附加特定的錯誤處理程式:

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()需要啟用異常類。如果任何步驟失敗併發生異常,則鏈會中斷,並呼叫與丟擲的異常型別匹配的錯誤處理程式。

根據訊號建立QFuture

給定一個帶有signal 的QObject基於類,您可以透過以下方式將此用作Future類:MyObjectvoid mySignal(int)

QFuture intFuture = QtFuture::connect(&object, &MyObject::mySignal);

現在,您可以將延續,失敗或取消處理程式附加到最終的結果上。

請注意,最終結果的型別與signal的自變數型別匹配。如果沒有引數,則 返回 QFuture。如果有多個引數,則結果儲存在中std::tuple。

讓我們回到影象處理示例的第一步(即下載),以瞭解這在實踐中如何有用。有很多方法可以實現它,我們將使用QNetworkAccessManager來發送網路請求並獲取資料:

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;

}

但是上面的阻塞等待不是很好,如果我們可以避開它那就更好了,比如說“當QNetworkAccessManager獲取資料時,建立一個影象,然後對其進行處理然後顯示”。我們可以透過將網路訪問管理器的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)

。。。

您會注意到,現在我們不再使用QtConcurrent::run()非同步下載而是在新執行緒中返回資料,我們只是連線到QNetworkAccessManager::finished()訊號,從而開始了計算鏈。還請注意以下行中的其他引數:

。then(QtFuture::Launch::Async, createImage)

預設情況下。then()在父程序執行所在的同一執行緒(在本例中為主執行緒)中呼叫by附加的延續。現在,我們不再使用QtConcurrent::run()非同步啟動鏈,我們需要傳遞附加QtFuture::Launch::Async引數,以在單獨的執行緒中啟動連續鏈,並避免阻塞UI。

建立一個QFuture

到目前為止,在QFuture內部建立和儲存值的唯一“官方”方法是QtConcurrent中的一種方法。所以QtConcurrent以外,QFuture不是很有用。在Qt 6中,將Andrei Golubev引入了“Setter”, QFuture: QPromise的對應物。它可用於為非同步計算設定值,進度和異常,以後可透過訪問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的變化

-現在,您可以為QtConcurrent的所有方法設定自定義執行緒池,而不是始終在全域性執行緒池上執行它們並可能阻止其他任務的執行。

-對映和過濾器縮小演算法現在可以採用初始值,因此您不必為沒有預設建構函式的型別做變通辦法。

- QtConcurrent::run進行了改進,可以處理可變數量的引數和僅移動型別。

此外,我們在QtConcurrent中添加了兩個新的API,以為使用者提供更大的靈活性。讓我們更詳細地看一下。

QtConcurrent :: runWithPromise

QtConcurrent::runWithPromise()Jarek Kobus開發的新方法是QtConcurrent框架的另一個不錯的補充。它非常類似於QtConcurrent::run(),不同之處在於,它使QPromise與給定任務相關聯的物件可供使用者訪問。

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()使用者可以更好地控制任務,並且可以響應取消或暫停請求,進行進度報告等操作,而這些使用QtConcurrent::run()是不可能實現的。

QtConcurrent ::任務

QtConcurrent::task()提供了一個流暢的介面,用於在單獨的執行緒中執行任務。它對於QtConcurrent::run()是更為現代的替代方案,並配置任務的方式也更為方便。您可以使用任何順序指定引數,跳過不需要的引數,等等,而不是使用少數幾個引數之一來傳遞引數來執行任務。例如:

QFuture future = QtConcurrent::task(doSomething)

。withArguments(1, 2, 3)

。onThreadPool(pool)

。withPriority(10)

。spawn();

請注意,與run()不同,您還可以為任務傳遞優先順序。

本篇文章中的內容你都學會了嗎?如果這篇文章沒能滿足你的需求、點選

獲取更多文章教程

!點選

下載Qt

免費試用版!