跨平台开发框架Qt最新资讯:Qt6中的异步API
Qt(发音为“ cute”,而不是“ cu-tee”)是一个跨平台框架,通常用作图形工具包,它不仅创建CLI应用程序中非常有用。而且它也可以在三种主要的台式机操作系统以及移动操作系统(如Symbian,Nokia Belle,Meego Harmattan,MeeGo或BB10)以及嵌入式设备,Android(Necessitas)和iOS的端口上运行。现在我们为你提供了免费的试用版。赶快点击下载Qt最新试用版吧>>
大家可能知道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<QImage> 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<void>。如果有多个参数,则结果存储在中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免费试用吧!更多Qt类开发工具QtitanRibbon、QtitanChart、QtitanNavigation、QtitanDocking、QtitanDataGrid在线订购现直降1000元,欢迎咨询慧都获取更多优惠>>