On this page

TaskTree Image Scaling

Demonstrates how to execute For loop's iterations in parallel to asynchronously download and scale images using TaskTree.

This example shows how to use Qt TaskTree classes to download a collection of images from the network and scale them without blocking the UI. It demonstrates how to use the For element to run subtasks in a parallel loop over QList items, and how to use the Storage element to share data between tasks.

This example provides an alternative implementation for the image scaling example in Qt Concurrent.

Image downloading and scaling.

Application workflow

When the user presses the Add URLs button, the following method runs:

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

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

The method runs DownloadDialog. When the user accepts the dialog, the method gets the list of selected URLs, clears the displayed images, and prepares a grid for new images by calling initLayout(urls.size()).

For each item in the urls list, the app:

To iterate over the urls list, the app uses an iterator element of ListIterator type. The QByteArray data received after downloading is stored in the storage element of Storage<QByteArray> type, which is used to pass the data to the scale function.

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

The recipe that describes the workflow looks like this:

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

Here is a summary of all elements used in the recipe:

ElementPurpose
ForTop level loop iterating over a list of urls.
DoDescribes the body of each iteration.
finishAllAndSuccessInstructs the running tree to continue execution even on errors, and to report success afterward.
parallelCauses that all Do's body direct children will execute in parallel, including all iterations of the top level For element.

This means that all iterations will start simultaneously.

onGroupSetup(onRootSetup)Invokes onRootSetup handler when the first iteration is about to be executed.
onGroupDone(onRootDone)Invokes onRootDone handler when all the iterations are finished.
GroupA subgroup, executed for each iteration. By default, all its children execute in sequence.
storageInstructs the running task tree to instantiate the underlaying QByteArray instance when the subgroup is entered.

This means the running task tree instantiates a separate QByteArray copy for each iteration.

QNetworkReplyWrapperTaskThe first asynchronous task executed in sequence for each iteration.

Starts downloading the QByteArray data for a given url. When the download is about to start, the onDownloadSetup is invoked to setup the input url for download.

When it's finished, the onDownloadDone is invoked. In case of successful execution, the downloaded QByteArray data is stored inside storage element.

QConcurrentCallTask<QImage>The second asynchronous task executed in sequence for each iteration. It's executed only after successful execution of the first task.

Executes a function in a separate thread, passing the stored QByteArray data of the downloaded image. When the function is about to start, the onScaleSetup is invoked to setup the QByteArray data for scaling.

When it's finished, the onScaleDone is invoked. In case of successful execution it displays the thumbnail in a grid.

Update the status bar

The following shows how all handlers are implemented:

    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);
    };

When the first iteration starts, the onRootSetup handler updates the status bar message and enables the Cancel button. When the last iteration finishes, the onRootDone handler updates the status bar message and disables the Cancel button.

Download and scale images

Each iteration runs in parallel with others and consists of two sequential tasks. The first task in each iteration is QNetworkReplyWrapperTask. Here is how its setup and done handlers are implemented:

    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."));
    };

The onDownloadSetup handler runs when QNetworkReplyWrapper is about to start. It sets up the network access manager and network request. The handler captures the iterator, which gives access to the current address from the urls list.

The onDownloadDone handler runs when QNetworkReplyWrapper finishes. It captures the storage and iterator. If the task succeeds, it stores the received QByteArray data in storage. The task tree creates a separate QByteArray instance for each iteration. Dereferencing storage gives access to the QByteArray instance for the current iteration. If the task fails or is canceled, the handler shows the error message for the label immediately. The index of the current iteration is returned by iterator.iteration().

The second task in each iteration is QConcurrentCallTask<QImage>. Its setup and done handlers are as follows:

    const auto onScaleSetup = [storage](QConcurrentCall<QImage> &task) {
        task.setConcurrentCallData(&scale, *storage);
    };
    const auto onScaleDone = [this, iterator](const QConcurrentCall<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."));
    };

Inside onScaleSetup, the handler schedules a concurrent call to the scale function, passing the QByteArray data received from the first task as an argument. This is done by dereferencing the captured storage. The scale function runs in a separate thread.

The onScaleDone handler runs when the scale function finishes. onScaleDone is called from the main thread. If scaling succeeds, the handler assigns the scaled image to the appropriate label. Otherwise, the handler shows an error message.

The following shows how the scale function is implemented:

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));
}

The function takes a QPromise to the result type as an argument. The promise is used to return the final result or to issue an error by canceling the promise's future.

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, see Qt Creator: Tutorial: Build and run.

Example project @ code.qt.io

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