Sur cette page

Mise à l'échelle des images avec TaskTree

Ce exemple montre comment exécuter des itérations de boucle « for » en parallèle pour télécharger et redimensionner des images de manière asynchrone à l'aide de TaskTree.

Cet exemple montre comment utiliser les classes Qt TaskTree pour télécharger une collection d’images depuis le réseau et les redimensionner sans bloquer l’interface utilisateur. Il illustre comment utiliser l’élément ` For ` pour exécuter des sous-tâches dans une boucle parallèle sur les éléments QList, et comment utiliser l’élément ` Storage ` pour partager des données entre les tâches.

Cet exemple propose une implémentation alternative à l’exemple de redimensionnement d’images présenté dans Qt Concurrent.

Grille des images téléchargées dont le statut est « Terminé »

Flux de travail de l’application

Lorsque l’utilisateur clique sur le bouton « Add URLs », la méthode suivante s’exécute :

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

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

La méthode exécute ` DownloadDialog`. Lorsque l’utilisateur valide la boîte de dialogue, la méthode récupère la liste des URL sélectionnées, efface les images affichées et prépare une grille pour les nouvelles images en appelant ` initLayout(urls.size())`.

Pour chaque élément de la liste « urls », l’application :

  • Télécharge l’image correspondant à une adresse donnée à l’aide de QNetworkReplyWrapper et stocke localement les données reçues de QByteArray.
  • Utilise QThreadFunction pour convertir les données QByteArray en une image QImage et la redimensionne en vignette dans un thread distinct.
  • Affiche la vignette obtenue dans la grille.

Pour parcourir la liste des urls, l’application utilise un élément « iterator » de type « ListIterator ». Les données « QByteArray » reçues après le téléchargement sont stockées dans l’élément « storage » de type « Storage<QByteArray> », qui sert à transmettre les données à la fonction de redimensionnement.

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

Le fichier « recipe » qui décrit le workflow se présente comme suit :

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

Voici un résumé de tous les éléments utilisés dans la recette :

ÉlémentObjectif
ForBoucle de niveau supérieur itérant sur une liste d’ urls.
DoDécrit le corps de chaque itération.
finishAllAndSuccessIndique à l'arborescence des tâches de poursuivre son exécution malgré les erreurs et de signaler la réussite une fois terminée.
parallelProvoque l'exécution en parallèle de tous les enfants directs du corps de l'Do, y compris toutes les itérations de l'élément de niveau supérieur For.

Cela signifie que toutes les itérations démarreront simultanément.

onGroupSetup(onRootSetup)Appelle le gestionnaire ` onRootSetup ` lorsque la première itération est sur le point d’être exécutée.
onGroupDone(onRootDone)Appelle le gestionnaire d'onRootDone s lorsque toutes les itérations sont terminées.
GroupUn sous-groupe, exécuté à chaque itération. Par défaut, tous ses enfants s'exécutent de manière séquentielle.
storageIndique à l’arborescence des tâches en cours d’exécution d’instancier l’instance sous-jacente d’ QByteArray lors de l’entrée dans le sous-groupe.

Cela signifie que l’arborescence de tâches en cours d’exécution instancie une copie distincte d’ QByteArray pour chaque itération.

QNetworkReplyWrapperTaskPremière tâche asynchrone s’exécutant de manière séquentielle à chaque itération.

Lance le téléchargement des données « QByteArray » pour un « url » donné. Lorsque le téléchargement est sur le point de démarrer, la méthode « onDownloadSetup » est appelée pour configurer l’ url d’entrée en vue du téléchargement.

Une fois l’opération terminée, la méthode ` onDownloadDone ` est appelée. En cas de réussite, les données ` QByteArray ` téléchargées sont stockées dans l’élément ` storage `.

QThreadFunctionTask<QImage>Deuxième tâche asynchrone exécutée à la suite pour chaque itération. Elle n’est exécutée qu’après l’exécution réussie de la première tâche.

Elle exécute une fonction dans un thread distinct, en lui transmettant les données QByteArray stockées de l’image téléchargée. Lorsque la fonction est sur le point de démarrer, la méthode onScaleSetup est appelée pour configurer les données QByteArray en vue de la mise à l’échelle.

Une fois l’opération terminée, la méthode ` onScaleDone ` est appelée. En cas de réussite, elle affiche la vignette dans une grille.

Mise à jour de la barre d'état

Voici comment tous les gestionnaires sont implémentés :

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

Lorsque la première itération démarre, le gestionnaire « onRootSetup » met à jour le message de la barre d'état et active le bouton « Cancel ». Lorsque la dernière itération se termine, le gestionnaire « onRootDone » met à jour le message de la barre d'état et désactive le bouton « Cancel ».

Téléchargement et redimensionnement des images

Chaque itération s’exécute en parallèle des autres et se compose de deux tâches séquentielles. La première tâche de chaque itération est « QNetworkReplyWrapperTask ». Voici comment ses gestionnaires « setup » et « done » sont implémentés :

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

Le gestionnaire ` onDownloadSetup ` s'exécute lorsque ` QNetworkReplyWrapper ` est sur le point de démarrer. Il configure le gestionnaire d'accès réseau et la requête réseau. Le gestionnaire récupère l'objet ` iterator`, qui donne accès à l'adresse actuelle issue de la liste ` urls `.

Le gestionnaire onDownloadDone s’exécute lorsque QNetworkReplyWrapper se termine. Il récupère les objets storage et iterator. Si la tâche aboutit, il stocke les données QByteArray reçues dans storage. L’arborescence des tâches crée une instance QByteArray distincte pour chaque itération. La déréférence de storage donne accès à l’instance QByteArray correspondant à l’itération en cours. Si la tâche échoue ou est annulée, le gestionnaire affiche immédiatement le message d’erreur associé à l’étiquette. L'index de l'itération en cours est renvoyé par iterator.iteration().

La deuxième tâche de chaque itération est QThreadFunctionTask<QImage>. Ses gestionnaires « setup » et « done » sont les suivants :

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

À l’intérieur de onScaleSetup, le gestionnaire planifie un appel à la fonction scale, en lui transmettant comme argument les données QByteArray reçues de la première tâche. Pour ce faire, il déréférence l’ storage capturé. La fonction scale s’exécute dans un thread distinct.

Le gestionnaire onScaleDone s’exécute lorsque la fonction scale a terminé. onScaleDone est appelée depuis le thread principal. Si la mise à l’échelle réussit, le gestionnaire attribue l’image mise à l’échelle à l’étiquette appropriée. Sinon, le gestionnaire affiche un message d’erreur.

Voici comment la fonction ` scale ` est implémentée :

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

La fonction prend comme argument une QPromise vers le type de résultat. La promise est utilisée pour renvoyer le résultat final ou pour signaler une erreur en annulant le future de la promesse.

Exécution de l’exemple

Vous pouvez exécuter l’exemple à partir de :

Exemple de projet sur 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.