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.

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:
- Downloads the image for a given address using QNetworkReplyWrapper and stores the received QByteArray data locally.
- Uses QConcurrentCall to convert the QByteArray data to a QImage and scales it to a thumbnail in a separate thread.
- Displays the resulting thumbnail in the grid.
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:
| Element | Purpose |
|---|---|
| For | Top level loop iterating over a list of urls. |
| Do | Describes the body of each iteration. |
| finishAllAndSuccess | Instructs the running tree to continue execution even on errors, and to report success afterward. |
| parallel | Causes 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. |
| Group | A subgroup, executed for each iteration. By default, all its children execute in sequence. |
storage | Instructs 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. |
| QNetworkReplyWrapperTask | The first asynchronous task executed in sequence for each iteration. Starts downloading the QByteArray data for a given When it's finished, the |
| 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 When it's finished, the |
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.
© 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.