本页

任务树图像缩放

演示如何并行运行 For 循环迭代,使用 TaskTree 异步下载和缩放图像。

本示例演示了如何使用 Qt TaskTree 类从网络下载图像集合并在不阻塞用户界面的情况下缩放图像。它演示了如何使用For 元素在QList 项目上并行循环运行子任务,以及如何使用Storage 元素在任务间共享数据。

本示例提供了《.NET Framework》中图像缩放示例的另一种实现方式。 Qt Concurrent.

带 "已完成 "状态的下载图像网格

应用程序工作流程

当用户按下Add URLs 按钮时,将运行以下方法:

    DownloadDialog dialog(this);
    if (dialog.exec() != QDialog::Accepted)
        return;

    const QList<QUrl> urls = dialog.getUrls();
    initLayout(urls.size());

该方法运行DownloadDialog 。当用户接受对话框时,该方法会获取所选 URL 的列表,清除显示的图像,并通过调用initLayout(urls.size()) 为新图像准备网格。

对于urls 列表中的每个项目,应用程序会

要遍历urls 列表,应用程序会使用ListIterator 类型的iterator 元素。下载后收到的QByteArray 数据存储在Storage<QByteArray> 类型的storage 元素中,用于将数据传递给缩放函数。

    const ListIterator iterator(urls);
    const Storage<QByteArray> storage;

描述工作流程的recipe 是这样的:

    const Group recipe = For (iterator) >> Do {
        finishAllAndSuccess,
        parallel,
        onGroupSetup(onRootSetup),
        Group {
            storage,
            QNetworkReplyWrapperTask(onDownloadSetup, onDownloadDone),
            QThreadFunctionTask<QImage>(onScaleSetup, onScaleDone)
        },
        onGroupDone(onRootDone)
    };
    taskTreeRunner.start(recipe);

以下是配方中使用的所有元素的摘要:

元素目的
For迭代urls 列表的顶级循环。
Do描述每次迭代的主体。
finishAllAndSuccess指示任务树在出现错误时继续运行,并在完成后报告成功。
parallel使Do 主体的所有直接子元素并行运行,包括顶层For 元素的所有迭代。

这意味着所有迭代将同时开始。

onGroupSetup(onRootSetup)当第一次迭代即将执行时,调用onRootSetup 处理程序。
onGroupDone(onRootDone)当所有迭代结束时,调用onRootDone 处理程序。
Group子组,每次迭代都会执行。默认情况下,它的所有子代都会依次执行。
storage当进入子群时,指示运行中的任务树实例化底层QByteArray 实例。

这意味着运行任务树会为每次迭代实例化一个单独的QByteArray 副本。

QNetworkReplyWrapperTask 任务每个迭代依次运行的第一个异步任务。

开始下载给定urlQByteArray 数据。当下载即将开始时,会调用onDownloadSetup 来设置下载的输入url

下载完成后,调用onDownloadDone 。如果执行成功,下载的QByteArray 数据将存储在storage 元素中。

QThreadFunctionTask<QImage>在每次迭代中依次执行的第二个异步任务。只有在第一个任务成功执行后才会执行。

在一个单独的线程中执行一个函数,传递已存储的下载图像的QByteArray 数据。当函数即将启动时,会调用onScaleSetup 来设置QByteArray 数据以进行缩放。

完成后,调用onScaleDone 。如果执行成功,它将在网格中显示缩略图。

更新状态栏

下面显示了所有处理程序的执行方式:

    const auto onRootSetup = [this] {
        statusBar->showMessage(tr("Downloading and Scaling..."));
        cancelButton->setEnabled(true);
    };
    const auto onRootDone = [this](DoneWith result) {
        const QString message = result == DoneWith::Cancel ? tr("Canceled.") : tr("Finished.");
        statusBar->showMessage(message);
        cancelButton->setEnabled(false);
    };

当第一次迭代开始时,onRootSetup 处理程序会更新状态栏信息并启用Cancel 按钮。当最后一次迭代结束时,onRootDone 处理程序会更新状态栏信息并禁用Cancel 按钮。

下载和缩放图像

每个迭代与其他迭代并行运行,由两个顺序任务组成。每个迭代中的第一个任务是QNetworkReplyWrapperTask 。以下是其设置和完成处理程序的实现方式:

    const auto onDownloadSetup = [this, iterator](QNetworkReplyWrapper &task) {
        task.setNetworkAccessManager(&qnam);
        task.setRequest(QNetworkRequest(*iterator));
    };
    const auto onDownloadDone = [this, storage, iterator](const QNetworkReplyWrapper &task,
                                                          DoneWith result) {
        const int it = iterator.iteration();
        if (result == DoneWith::Success)
            *storage = task.reply()->readAll();
        else if (result == DoneWith::Error)
            labels[it]->setText(tr("Download\nError.\nCode: %1.").arg(task.reply()->error()));
        else
            labels[it]->setText(tr("Canceled."));
    };

onDownloadSetup 处理程序在QNetworkReplyWrapper 即将启动时运行。它设置网络访问管理器和网络请求。该处理程序捕获iterator ,从而访问urls 列表中的当前地址。

onDownloadDone 处理程序在QNetworkReplyWrapper 结束时运行。它捕获storageiterator 。如果任务成功,它会将收到的QByteArray 数据存储在storage 中。任务树为每次迭代创建一个单独的QByteArray 实例。调用storage 可以访问当前迭代的QByteArray 实例。如果任务失败或取消,处理程序会立即显示标签的错误信息。当前迭代的索引由iterator.iteration() 返回。

每个迭代中的第二个任务是QThreadFunctionTask<QImage>。其设置和完成处理程序如下:

    const auto onScaleSetup = [storage](QThreadFunction<QImage> &task) {
        task.setThreadFunctionData(&scale, *storage);
    };
    const auto onScaleDone = [this, iterator](const QThreadFunction<QImage> &task,
                                              DoneWith result) {
        const int it = iterator.iteration();
        if (result == DoneWith::Success)
            labels[it]->setPixmap(QPixmap::fromImage(task.result()));
        else if (result == DoneWith::Error)
            labels[it]->setText(tr("Image\nData\nError."));
        else
            labels[it]->setText(tr("Canceled."));
    };

onScaleSetup 中,处理程序调度对scale 函数的调用,并将从第一个任务收到的QByteArray 数据作为参数传递。这是通过取消引用捕获的storage 来完成的。scale 函数在单独的线程中运行。

onScaleDone 处理程序在scale 函数结束时运行。onScaleDone 由主线程调用。如果缩放成功,处理程序会将缩放后的图像分配给相应的标签。否则,处理程序将显示错误信息。

下面显示了scale 函数的实现过程:

static void scale(QPromise<QImage> &promise, const QByteArray &data)
{
    const auto image = QImage::fromData(data);
    if (image.isNull())
        promise.future().cancel();
    else
        promise.addResult(image.scaled(100, 100, Qt::KeepAspectRatio));
}

函数的参数是QPromise 的结果类型。promise 用于返回最终结果,或通过取消承诺的未来来发出错误信息。

运行示例

运行示例 Qt Creator,打开Welcome 模式,并从Examples 中选择示例。更多信息,请参阅Qt Creator: 教程:构建并运行

示例项目 @ code.qt.io

© 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.