图像缩放
演示如何异步下载并调整图片大小。
本示例演示了如何使用QFuture 、QPromise 和QFutureWatcher 类从网络下载一组图片并对其进行缩放,同时不阻塞用户界面。

该应用程序包括以下步骤:
- 从用户指定的 URL 列表中下载图片。
- 调整图片大小。
- 以网格布局显示缩放后的图片。
让我们从下载开始:
QFuture<QByteArray> Images::download(const QList<QUrl> &urls)
{download() 方法接受一个URL列表,并返回一个QFuture 。QFuture 用于存储每张下载图片接收到的字节数组数据。为了将数据存储到QFuture 中,我们创建一个QPromise 对象,并报告其已开始下载以指示下载已启动:
QSharedPointer<QPromise<QByteArray>> promise(new QPromise<QByteArray>());
promise->start();
...
return promise->future();
}与该 Promise 关联的 Future 会被返回给调用方。
暂不深入探讨细节,这里只需注意该 Promise 对象被封装在QSharedPointer 中。这一点将在后文解释。
我们使用QNetworkAccessManager 来发送网络请求并下载每个 URL 的数据:
for (const auto &url : urls) {
QSharedPointer<QNetworkReply> reply(qnam.get(QNetworkRequest(url)));
replies.push_back(reply);接下来就是有趣的部分了:
QtFuture::connect(reply.get(), &QNetworkReply::finished).then([=] {
if (promise->isCanceled()) {
if (!promise->future().isFinished())
promise->finish();
return;
}
if (reply->error() != QNetworkReply::NoError) {
if (!promise->future().isFinished())
throw reply->error();
}
promise->addResult(reply->readAll());
// Report finished on the last download
if (promise->future().resultCount() == urls.size())
promise->finish();
}).onFailed([promise] (QNetworkReply::NetworkError error) {
promise->setException(std::make_exception_ptr(error));
promise->finish();
}).onFailed([promise] {
const auto ex = std::make_exception_ptr(
std::runtime_error("Unknown error occurred while downloading."));
promise->setException(ex);
promise->finish();
});
}
...我们不使用QObject::connect()方法订阅QNetworkReply 的信号,而是使用QtFuture::connect()。其工作原理与QObject::connect()类似,但会返回一个QFuture 对象,该对象在QNetworkReply::finished()信号触发后立即可用。这使我们能够附加后续操作和失败处理程序,正如示例中所示。
在通过.then() 附加的延续中,我们检查用户是否请求取消下载。如果是,则停止处理该请求。通过调用QPromise::finish() 方法,我们通知用户处理已完成。如果网络请求以错误结束,则抛出异常。 该异常将在通过.onFailed() 方法附加的故障处理程序中得到处理。请注意,我们有两个故障处理程序:第一个捕获网络错误,第二个捕获执行过程中抛出的所有其他异常。这两个处理程序都会将异常保存到 Promise 对象中(由download() 方法的调用者进行处理),并报告计算已完成。 另请注意,为简化起见,一旦发生错误,我们会中断所有待处理的下载任务。
如果请求未被取消且未发生错误,我们会从网络响应中读取数据,并将其添加到 Promise 对象的结果列表中:
...
promise->addResult(reply->readAll());
// Report finished on the last download
if (promise->future().resultCount() == urls.size())
promise->finish();
...如果 Promise 对象中存储的结果数量等于待下载的url的数量,则表示已无待处理请求,因此我们也会报告该 Promise 已完成。
如前所述,我们已将 Promise 封装在 `QSharedPointer` 中。由于 Promise 对象在处理各网络响应的处理程序之间共享,我们需要同时在多个位置复制并使用该 Promise 对象。因此,使用了 `QSharedPointer `。
download() 方法由Images::process 方法调用。当用户点击“添加URL”按钮时,该方法会被调用:
...
connect(addUrlsButton, &QPushButton::clicked, this, &Images::process);
...清除之前下载可能留下的残留数据后,我们会创建一个对话框,供用户指定要下载的图片 URL。根据指定的 URL 数量,我们初始化用于显示图片的布局并开始下载。download() 方法返回的 Future 会被保存,以便用户在需要时取消下载:
void Images::process()
{
// Clean previous state
replies.clear();
addUrlsButton->setEnabled(false);
if (downloadDialog->exec() == QDialog::Accepted) {
const auto urls = downloadDialog->getUrls();
if (urls.empty())
return;
cancelButton->setEnabled(true);
initLayout(urls.size());
downloadFuture = download(urls);
statusBar->showMessage(tr("Downloading..."));
...接下来,我们附加一个延续处理程序来处理缩放步骤。相关内容稍后详述:
downloadFuture
.then([this](auto) {
cancelButton->setEnabled(false);
updateStatus(tr("Scaling..."));
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
downloadFuture.results()));
})
...之后,我们挂接onCanceled()和onFailed()处理程序:
.onCanceled([this] {
updateStatus(tr("Download has been canceled."));
})
.onFailed([this](QNetworkReply::NetworkError error) {
updateStatus(tr("Download finished with error: %1").arg(error));
// Abort all pending requests
abortDownload();
})
.onFailed([this](const std::exception &ex) {
updateStatus(tr(ex.what()));
})
...如果用户点击了“取消”按钮,通过.onCanceled() 方法附加的处理程序将被调用:
...
connect(cancelButton, &QPushButton::clicked, this, &Images::cancel);
...cancel() 方法仅会中止所有待处理的请求:
void Images::cancel()
{
statusBar->showMessage(tr("Canceling..."));
downloadFuture.cancel();
abortDownload();
}如果在前面的某个步骤中发生了错误,则会调用通过.onFailed() 方法附加的处理程序。例如,如果在下载步骤中,网络错误已被保存到 Promise 中,该错误将传播到以QNetworkReply::NetworkError 为参数的处理程序中。
如果 `downloadFuture ` 未被取消且未报告任何错误,则执行缩放后续操作。
由于缩放操作可能计算量较大,且我们不希望阻塞主线程,因此使用QtConcurrent::run() 在新线程中启动缩放步骤。
scalingWatcher.setFuture(QtConcurrent::run(Images::scaled,
downloadFuture.results()));由于缩放是在单独的线程中启动的,用户可能会在缩放操作进行期间决定关闭应用程序。为了优雅地处理此类情况,我们将QtConcurrent::run()返回的QFuture 传递给QFutureWatcher 实例。
监视器的QFutureWatcher::finished 信号连接到Images::scaleFinished 的槽:
connect(&scalingWatcher, &QFutureWatcher<QList<QImage>>::finished,
this, &Images::scaleFinished);该槽负责在用户界面中显示缩放后的图像,并处理缩放过程中可能发生的错误:
void Images::scaleFinished()
{
const OptionalImages result = scalingWatcher.result();
if (result.has_value()) {
const auto scaled = result.value();
showImages(scaled);
updateStatus(tr("Finished"));
} else {
updateStatus(tr("Failed to extract image data."));
}
addUrlsButton->setEnabled(true);
}错误报告通过在Images::scaled() 方法中返回一个optional来实现:
Images::OptionalImages Images::scaled(const QList<QByteArray> &data)
{
QList<QImage> scaled;
for (const auto &imgData : data) {
QImage image;
image.loadFromData(imgData);
if (image.isNull())
return std::nullopt;
scaled.push_back(image.scaled(100, 100, Qt::KeepAspectRatio));
}
return scaled;
}此处的Images::OptionalImages 类型只是std::optional 的 typedef:
using OptionalImages = std::optional<QList<QImage>>;注意:我们 无法使用 `.onFailed()` 处理程序来处理异步缩放操作中的错误,因为该处理程序必须在 UI 线程中 `Images ` 对象的上下文中执行。如果用户在异步计算完成时关闭应用程序,`Images ` 对象将被销毁,而从延续中访问其成员将导致崩溃。 使用QFutureWatcher 及其信号可以避免这个问题,因为当QFutureWatcher 被销毁时,信号会被断开,因此相关的槽函数绝不会在已销毁的上下文中被执行。
其余代码非常简单,您可以查看示例项目以获取更多详细信息。
运行示例
您可以通过以下方式运行示例:
- Qt Creator
打开“Welcome ”模式,并从“Examples ”中选择该示例。有关更多信息,请参阅Qt Creator: 教程:构建和运行。
- Qt Extension for Visual Studio Code
在“Command Palette ”中运行Qt: Open Qt examples 命令,并从列表中选择该示例。有关更多信息,请参阅Qt Extension for Visual Studio Code :教程:构建和运行。
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.