Sur cette page

Qt TaskTree C++ Classes

Contient une bibliothèque TaskTree à usage général. En savoir plus...

Ce module est en phase de préversion technologique et est susceptible d'être modifié.

Ce module a été introduit dans Qt 6.11.

Espaces de noms

QtTaskTree

Regroupe toutes les classes et fonctions globales du module TaskTree

Classes

QtTaskTree::Do

Élément de corps utilisé avec les constructions « For » et « When »

QtTaskTree::Else

Élément « else » utilisé dans les expressions conditionnelles

QtTaskTree::ElseIf

Un élément « else if » utilisé dans les expressions conditionnelles

QtTaskTree::ExecutableItem

Classe de base pour les éléments de tâche exécutables

QtTaskTree::ExecutionMode

Élément de groupe décrivant le mode d'exécution

QtTaskTree::For

Un élément de boucle « for »

QtTaskTree::Forever

Boucle infinie de sous-tâches

QtTaskTree::ForeverIterator

Itérateur infini à utiliser à l’intérieur d’un élément « for »

QtTaskTree::Group

Représente l’élément de base permettant de composer des recettes déclaratives décrivant comment exécuter et gérer une arborescence imbriquée de tâches asynchrones

QtTaskTree::GroupItem

Représente l’élément de base pouvant faire partie de n’importe quel groupe

QtTaskTree::If

Un élément « if » utilisé dans des expressions conditionnelles

QtTaskTree::Iterator

Classe de base à utiliser comme itérateur à l'intérieur d'un élément « For »

QtTaskTree::ListIterator

Itérateur de liste à utiliser à l'intérieur d'un élément « For »

QtTaskTree::ObjectSignal

Structure décrivant une sous-classe de QObject et son signal

QtTaskTree::ParallelLimit

Mode d’exécution parallèle avec une limite personnalisée

QtTaskTree::QBarrier

Une tâche asynchrone qui se termine à la demande

QtTaskTree::QCustomTask

Un modèle de classe utilisé pour déclarer des éléments de tâche personnalisés et définir leurs gestionnaires de configuration et de fin

QtTaskTree::QDefaultTaskAdapter

Un modèle de classe fournissant un adaptateur de tâche par défaut utilisé dans QCustomTask

QtTaskTree::QMappedTaskTreeRunner

Un contrôleur d'exécution d'arborescence de tâches mappée avec un type de clé donné

QtTaskTree::QNetworkReplyWrapper

Un wrapper autour de QNetworkReply et de QNetworkAccessManager

QtTaskTree::QParallelTaskTreeRunner

Un contrôleur d'exécution d'arborescence de tâches parallèle

QtTaskTree::QProcessTaskDeleter

Un destructeur personnalisé pour QProcess, utilisé par QProcessTask

QtTaskTree::QSequentialTaskTreeRunner

Un contrôleur d'exécution séquentiel d'arborescence de tâches

QtTaskTree::QSingleTaskTreeRunner

Un contrôleur d'exécution d'arborescence de tâches unique

QtTaskTree::QStartedBarrier

Un QBarrier lancé avec une limite donnée

QtTaskTree::QSyncTask

Exécute de manière synchrone un gestionnaire personnalisé entre d'autres tâches

QtTaskTree::QTaskInterface

Classe d'aide utilisée lors de l'adaptation de l'interface d'une tâche personnalisée

QtTaskTree::QTaskTree

Exécute l’arborescence des tâches asynchrones définies de manière déclarative

QtTaskTree::QTcpSocketWrapper

Un wrapper autour de QTcpSocket

QtTaskTree::QThreadFunction

Un modèle de classe contrôlant l'exécution d'une fonction dans un thread séparé via QtConcurrent::run()

QtTaskTree::QThreadFunctionBase

Une classe de base pour le modèle de classe QThreadFunction

QtTaskTree::RepeatIterator

Itérateur répétitif à utiliser à l'intérieur d’un élément « For »

QtTaskTree::Storage

Un modèle de classe pour l'échange de données personnalisées dans l'arborescence des tâches en cours d'exécution

QtTaskTree::Then

Un élément « then » utilisé dans les expressions conditionnelles

QtTaskTree::UntilIterator

Itérateur conditionnel à utiliser à l'intérieur de l'élément « For »

QtTaskTree::When

Un élément retardant l'exécution d'un corps jusqu'au franchissement d'une barrière

Description détaillée

Utilisez la bibliothèque TaskTree pour créer des recettes décrivant les tâches asynchrones à exécuter, puis utilisez ces recettes dans QTaskTree pour les exécuter.

Les recettes sont des descriptions déclaratives des types de tâches à créer et à exécuter, par exemple : QProcess, QNetworkReplyWrapper ou QThreadFunction<ReturnType>, et précisent si elles doivent s'exécuter en séquence ou en parallèle. Au sein des recettes, vous pouvez définir différents chemins de poursuite selon que la tâche précédente s'est terminée avec succès ou par une erreur. Il est également possible d’imbriquer des tâches dans des éléments « Group », et chaque « Group » peut exécuter ses tâches selon son propre mode d’exécution ou sa propre politique de flux de travail. Les recettes forment les structures arborescentes des tâches.

Tâches asynchrones

Une tâche asynchrone est toute tâche qui peut être lancée et qui se termine ultérieurement par un succès ou une erreur. « Ultérieurement » signifie qu’après le lancement de la tâche, le contrôle revient à la boucle d’événements en cours d’exécution. Nous ne bloquons pas le thread appelant jusqu’à ce que la tâche soit terminée. Pour utiliser l’arborescence des tâches, une boucle d’événements doit être active.

Exemples de tâches asynchrones :

Arbre des recettes et des tâches

Pour mieux comprendre ce que sont la recette et l’arborescence des tâches, utilisons l’analogie de la cartouche et du lecteur. Lorsque nous écrivons une recette, c’est comme si nous construisions une cartouche : nous préparons simplement une description détaillée de ce que le lecteur devra faire plus tard, une fois que la cartouche sera insérée dans le lecteur (arborescence des tâches) et lancée. La recette elle-même n’est qu’une description déclarative destinée à l’arbre des tâches, indiquant ce que celui-ci doit faire lorsque la recette lui est transmise et qu’il est lancé. La recette ne fait rien par elle-même sans arbre des tâches : c’est comme une cartouche sans lecteur.

Voici un bref résumé des responsabilités respectives de la recette et de l’arbre de tâches.

La recette (cartouche) décrit :

  • les tâches qui doivent être créées dynamiquement par l'arborescence des tâches en cours d'exécution (via QCustomTask)
  • dans quel ordre
  • quelles structures de données doivent être créées dynamiquement par l'arborescence des tâches en cours d'exécution (via Storage)
  • comment configurer chaque tâche avant son lancement
  • comment collecter les données une fois les tâches terminées
  • le mode d'exécution (les tâches doivent s'exécuter en séquence ou en parallèle)
  • la politique de workflow

Arborescence des tâches (joueur) :

  • lit la recette et crée automatiquement des tâches et des structures de données
  • gère le cycle de vie des tâches et des structures de données créées
  • exécute les continuations
  • choisit différents chemins en fonction des résultats des tâches terminées et des règles de workflow
  • fournit des informations de base sur l'avancement

Tâches personnalisées

Étant donné qu’une recette décrit à l’arborescence des tâches quelles tâches elle doit créer lors de son lancement, nous ne pouvons pas créer ces tâches directement au sein d’une recette. Nous devons plutôt utiliser une approche déclarative pour indiquer à l’arborescence des tâches de créer et de lancer ces tâches pour nous à un moment ultérieur. Par exemple, si nous souhaitons que l’arborescence de tâches crée et lance « QProcess », nous le décrivons en plaçant un élément « QProcessTask » à l’intérieur d’une recette. « QProcessTask » est un alias de « QCustomTask » <QProcess>. Chaque type de tâche doit fournir son « QCustomTask » <Type> correspondant afin qu’il puisse être utilisé à l’intérieur des recettes.

Le tableau suivant présente quelques tâches personnalisées intégrées, prêtes à être placées dans des recettes :

Tâche personnalisée (utilisée dans les recettes)Classe de tâche (créée par l’arborescence des tâches en cours d’exécution)Brève description
QProcessTaskQProcessLance le processus.
QThreadFunctionTask<ReturnType>QThreadFunction<ReturnType>Lance une tâche asynchrone, s'exécutant dans un thread distinct.
QTaskTreeTaskQTaskTreeLance une arborescence de tâches imbriquées.
QNetworkReplyWrapperTaskQNetworkReplyWrapperLance un téléchargement réseau.
QTcpSocketWrapperTaskQTcpSocketWrapperLance une connexion TCP.

Consultez QTaskInterface et Task Adapters pour plus d'informations sur la manière d'adapter une tâche spécifique en vue de son utilisation dans des recettes.

Exemple de recette

Le fichier QTaskTree comporte un élément de niveau supérieur Group, également appelé « recette », qui peut contenir un nombre illimité de tâches de différents types, telles que QProcessTask, QNetworkReplyWrapperTask ou QThreadFunctionTask<ReturnType> :

const Group recipe {
    QProcessTask(...),
    QNetworkReplyWrapperTask(...),
    QThreadFunctionTask<int>(...)
};

QTaskTree *taskTree = new QTaskTree(recipe);
connect(taskTree, &QTaskTree::done, ...);  // finish handler
taskTree->start();

La recette ci-dessus se compose d’un élément de niveau supérieur de type « Group » qui contient des tâches de type « QProcessTask », « QNetworkReplyWrapperTask » et « QThreadFunctionTask » <int>. Une fois « taskTree->start() » appelée, les tâches sont créées et exécutées en chaîne, en commençant par « QProcess ». Lorsque la tâche « QProcess » s’achève avec succès, la tâche « QNetworkReplyWrapper » est lancée. Enfin, lorsque la tâche « network » s’achève avec succès, la tâche « QThreadFunction » <int> est lancée.

Lorsque la dernière tâche en cours d’exécution se termine avec succès, l’arborescence de tâches est considérée comme ayant été exécutée avec succès et le signal QTaskTree::done() est émis avec DoneWith::Success. Lorsqu’une tâche se termine par une erreur, l’exécution de l’arborescence de tâches est interrompue et les tâches restantes sont ignorées. L’arborescence de tâches se termine par une erreur et envoie le signal QTaskTree::done() avec DoneWith::Error.

Groupes

Le parent de Group le considère comme une tâche unique. À l’instar des autres tâches, le groupe peut être lancé et se terminer par un succès ou une erreur. Les éléments Group peuvent être imbriqués pour créer une structure arborescente :

const Group recipe {
    Group {
        parallel,
        QProcessTask(...),
        QThreadFunctionTask<int>(...)
    },
    QNetworkReplyWrapperTask(...)
};

L’exemple ci-dessus diffère du premier exemple en ce que l’élément de niveau supérieur comporte un sous-groupe contenant l’ QProcessTask et l’ QThreadFunctionTask<int>. Le sous-groupe est un élément frère de l’ QNetworkReplyWrapperTask à la racine. Le sous-groupe contient un élément parallel supplémentaire qui indique à son Group d’exécuter ses tâches en parallèle.

Ainsi, lorsque l’ QTaskTree e lance la recette ci-dessus, les éléments QProcess et QThreadFunction<int> démarrent immédiatement et s’exécutent en parallèle. Comme le groupe racine ne contient pas d’élément parallel, ses tâches enfants directes s’exécutent en séquence. Par conséquent, l’ QNetworkReplyWrapper e démarre lorsque l’ensemble du sous-groupe a terminé. Le groupe est considéré comme terminé lorsque toutes ses tâches sont terminées. L’ordre dans lequel les tâches se terminent n’a pas d’importance.

Ainsi, selon la tâche qui dure le plus longtemps (QProcess ou QThreadFunction<int>), les scénarios suivants peuvent se produire :

Scénario 1Scénario 2
Le groupe racine démarreLe groupe racine démarre
Démarrage du sous-groupeLe sous-groupe démarre
QProcess commenceQProcess démarre
QThreadFunction<int> démarreQThreadFunction<int> démarre
......
QProcess finitQThreadFunction<int> se termine
......
QThreadFunction<int> se termineQProcess finishes
Le sous-groupe se termineLe sous-groupe se termine
QNetworkReplyWrapper démarreQNetworkReplyWrapper démarre
......
QNetworkReplyWrapper se termineQNetworkReplyWrapper se termine
Le groupe racine se termineLe groupe racine se termine

Les différences entre les scénarios sont indiquées en gras. Les trois points signifient qu'un laps de temps indéterminé s'écoule entre l'événement précédent et le suivant (une ou plusieurs tâches continuent de s'exécuter). L'absence de points entre les événements signifie qu'ils se produisent de manière synchrone.

Les scénarios présentés partent du principe que toutes les tâches s’exécutent correctement. Si une tâche échoue pendant son exécution, l’arborescence des tâches se termine par une erreur. En particulier, lorsque QProcess se termine par une erreur alors que QThreadFunction<int> est encore en cours d’exécution, QThreadFunction<int> est automatiquement annulée, le sous-groupe se termine par une erreur, QNetworkReplyWrapper est ignorée et l’arborescence se termine par une erreur.

Gestionnaires de tâches

Utilisez les gestionnaires de tâches Task pour configurer une tâche en vue de son exécution et pour permettre la lecture des données de sortie de la tâche lorsqu’elle se termine avec succès ou par une erreur.

Gestionnaire de démarrage de la tâche

Lorsqu'un objet tâche est créé et avant son lancement, l'arborescence des tâches appelle un gestionnaire de configuration, qui peut être fourni par l'utilisateur. Le gestionnaire de configuration doit toujours prendre une référence à l'objet de classe de tâche associé :

const auto onSetup = [](QProcess &process) {
    process.setProgram("sleep");
    process.setArguments({"3"});
};
const Group root {
    QProcessTask(onSetup)
};

Vous pouvez modifier l’ QProcess e transmise dans le gestionnaire de configuration, afin que l’arborescence des tâches puisse lancer le processus conformément à votre configuration. Vous ne devez pas appeler la méthode ` process.start(); ` dans le gestionnaire de configuration, car l’arborescence des tâches l’appelle elle-même lorsque cela est nécessaire. Le gestionnaire de configuration est facultatif. Lorsqu’il est utilisé, il doit être le premier argument du constructeur de la tâche.

Le gestionnaire de configuration peut, s’il le souhaite, renvoyer une SetupResult. L’ SetupResult renvoyée influence le comportement ultérieur au démarrage d’une tâche donnée. Les valeurs possibles sont :

SetupResult ValeurBrève description
ContinueLa tâche sera lancée normalement. Il s’agit du comportement par défaut lorsque le gestionnaire de configuration ne renvoie pas d’ SetupResult (c’est-à-dire que son type de retour est void).
StopWithSuccessLa tâche ne sera pas lancée et signalera un succès à son parent.
StopWithErrorLa tâche ne sera pas lancée et signalera une erreur à sa tâche parente.

Ceci est utile pour exécuter une tâche uniquement lorsqu’une condition est remplie et que les données nécessaires à l’évaluation de cette condition ne sont pas connues avant la fin des tâches lancées précédemment. De cette manière, le gestionnaire de configuration décide dynamiquement s’il faut lancer la tâche correspondante normalement ou l’ignorer et signaler une réussite ou une erreur. Pour plus d’informations sur l’échange de données entre tâches, voir Storage.

Gestionnaire « Done » de la tâche

Lorsqu’une tâche en cours d’exécution se termine, l’arborescence des tâches invoque un gestionnaire « done » fourni de manière facultative. Ce gestionnaire doit prendre une référence const vers l’objet de classe de tâche associé :

const auto onSetup = [](QProcess&process) {
    process.setProgram("sleep");
    process.setArguments({"3"});
};
const auto onDone = [](const QProcess&process, DoneWith result) {
    if (result== DoneWith::Success)
        qDebug() << "Success" << process.cleanedStdOut();
   sinon
        qDebug() << "Failure" << process.cleanedStdErr();
};
const Group root {
    QProcessTask(onSetup, onDone)
};

Le gestionnaire « done » peut collecter les données de sortie provenant de ` QProcess` et les stocker en vue d'un traitement ultérieur ou effectuer des actions supplémentaires.

Remarque : si le gestionnaire de configuration de la tâche renvoie StopWithSuccess ou StopWithError, le gestionnaire « done » n'est pas appelé.

Gestionnaires de groupe

À l'instar des gestionnaires de tâches, les gestionnaires de groupes vous permettent de configurer un groupe à exécuter et d'appliquer davantage d'actions lorsque l'ensemble du groupe s'achève avec succès ou en cas d'erreur.

Gestionnaire de démarrage du groupe

L'arborescence des tâches invoque le gestionnaire de démarrage du groupe avant de lancer les tâches filles. Le gestionnaire de groupe ne prend aucun argument :

const auto onSetup = [] {
    qDebug() << "Entering the group";
};
const Group root {
    onGroupSetup(onSetup),
    QProcessTask(...)
};

Le gestionnaire de configuration du groupe est facultatif. Pour définir un gestionnaire de configuration de groupe, ajoutez un élément ` onGroupSetup()` à un groupe. L’argument de ` onGroupSetup()` est un gestionnaire utilisateur. Si vous ajoutez plusieurs éléments ` onGroupSetup()` à un groupe, une assertion est déclenchée lors de l’exécution, accompagnée d’un message d’erreur.

Tout comme le gestionnaire de démarrage d’une tâche, le gestionnaire de démarrage d’un groupe peut renvoyer ` SetupResult`. La valeur renvoyée par ` SetupResult ` affecte le comportement de démarrage de l’ensemble du groupe. Si vous ne spécifiez pas de gestionnaire de démarrage de groupe, ou si son type de retour est `void`, l’action par défaut du groupe est ` Continue`, ce qui signifie que toutes les tâches sont lancées normalement. Dans le cas contraire, lorsque le gestionnaire de démarrage renvoie « StopWithSuccess » ou « StopWithError », les tâches ne sont pas lancées (elles sont ignorées) et le groupe lui-même signale respectivement une réussite ou une erreur, en fonction de la valeur renvoyée.

const Group root {
    onGroupSetup([] { qDebug() << "Root setup"; }),
    Group {
        onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }),
        QProcessTask(...) // Process 1
    },
    Group {
        onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }),
        QProcessTask(...) // Process 2
    },
    Group {
        onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }),
        QProcessTask(...) // Process 3
    },
    QProcessTask(...) // Process 4
};

Dans l’exemple ci-dessus, tous les sous-groupes d’un groupe racine définissent leurs gestionnaires de configuration. Le scénario suivant part du principe que tous les processus lancés se terminent avec succès :

ScénarioCommentaire
Le groupe racine démarreNe renvoie pas de SetupResult, ses tâches sont donc exécutées.
Le groupe 1 démarreRenvoie Continue, ses tâches sont donc exécutées.
Le processus 1 démarre
......
Le processus 1 se termine (succès)
Le groupe 1 se termine (succès)
Le groupe 2 démarreRenvoie StopWithSuccess, donc le processus 2 est ignoré et le groupe 2 signale la réussite.
Le groupe 2 se termine (succès)
Le groupe 3 démarreRenvoie StopWithError : le processus 3 est donc ignoré et le groupe 3 signale une erreur.
Le groupe 3 se termine (erreur)
Le groupe racine se termine (erreur)Le groupe 3, qui est un enfant direct du groupe racine, s'est terminé par une erreur ; le groupe racine arrête donc son exécution, ignore le processus 4, qui n'a pas encore démarré, et signale une erreur.

Gestionnaire « Done » des groupes

Le gestionnaire de fin d’un « Group » est exécuté après l’exécution réussie ou échouée de ses tâches. La valeur finale signalée par le groupe dépend de son « Workflow Policy ». Le gestionnaire peut appliquer d’autres actions nécessaires. Le gestionnaire de fin est défini à l’intérieur de l’élément « onGroupDone() » d’un groupe. Il peut prendre l’argument facultatif « DoneWith », indiquant si l’exécution a réussi ou échoué :

const Group root {
    onGroupSetup([] { qDebug() << "Configuration racine"; }),
    QProcessTask(...),
    onGroupDone([](DoneWith result) {
        if (result== DoneWith::Success)
            qDebug() << "Root finished with success";
       sinon
            qDebug() << "Root finished with an error";
    })
};

Le gestionnaire « done » du groupe est facultatif. Si vous ajoutez plusieurs appels à onGroupDone() à un groupe, une assertion est déclenchée lors de l'exécution, accompagnée d'un message d'erreur.

Remarque : même si le gestionnaire de configuration du groupe renvoie StopWithSuccess ou StopWithError, le gestionnaire « done » du groupe est appelé. Ce comportement diffère de celui du gestionnaire « done » d’une tâche et est susceptible d’évoluer à l’avenir.

Autres éléments du groupe

Un groupe peut contenir d'autres éléments décrivant le flux de traitement, tels que les éléments « execution mode » ou « workflow policy ». Il peut également contenir des éléments de stockage chargés de collecter et de partager des données communes personnalisées recueillies pendant l'exécution du groupe.

Mode d'exécution

L'élément « mode d'exécution » d'un groupe spécifie la manière dont les tâches enfants directes du groupe sont lancées. Les modes d'exécution les plus courants sont « sequential » et « parallel ». Il est également possible de spécifier la limite du nombre de tâches s'exécutant en parallèle à l'aide de l'élément « ParallelLimit ».

Dans tous les modes d'exécution, un groupe lance les tâches dans l'ordre dans lequel elles apparaissent.

Si un enfant d’un groupe est lui-même un groupe, ce groupe enfant exécute ses tâches selon son propre mode d’exécution.

Politique de workflow

L'élément de politique de workflow dans un « Group » spécifie comment le groupe doit se comporter lorsque l'une des tâches de ses enfants directs se termine. Pour une description détaillée des politiques possibles, consultez WorkflowPolicy.

Si un enfant d’un groupe est lui-même un groupe, ce groupe enfant exécute ses tâches conformément à sa propre politique de workflow.

Stockage

Utilisez l’élément « Storage » pour échanger des informations entre les tâches. Cela s’avère particulièrement utile en mode d’exécution séquentielle, lorsqu’une tâche a besoin de données provenant d’une autre tâche déjà terminée avant de pouvoir démarrer. Par exemple, une arborescence de tâches qui copie des données en les lisant depuis une source et en les écrivant vers une destination pourrait se présenter comme suit :

static QByteArray load(const QString&fileName) {... }
static void save(const QString&fileName, const QByteArray&array) {... }

static Group copyRecipe(const QString&source, const QString&destination)
{
    struct CopyStorage { // [1] structure personnalisée inter-tâches
        QByteArray content; // [2] données inter-tâches personnalisées
    };

    // [3] instance de la structure inter-tâches personnalisée gérable par l’arborescence des tâches
    const Storage<CopyStorage> storage;

    const auto onLoaderSetup = [source](QThreadFunction<QByteArray> &async) {
        async.setThreadFunctionData(&load, source);
    };
   // [4] au moment de l’exécution : l’arborescence des tâches active l’instance de [7] avant d’appeler le gestionnaire
    const auto onLoaderDone = [storage](const QThreadFunction<QByteArray> &async) {
        storage->content = async.result(); // [5] le chargeur stocke le résultat dans storage
    };

   // [4] exécution : l'arborescence des tâches active l'instance de [7] avant d'appeler le gestionnaire
    const auto onSaverSetup = [storage, destination](QThreadFunction<void> &async) {
        const QByteArray content = storage->content; // [6] le programme de sauvegarde récupère les données dans storage
        async.setThreadFunctionData(&save, destination, content);
    };
    const auto onSaverDone = [](const QThreadFunction<void> &async) {
        qDebug() << "Save done successfully";
    };

    const Group root {
        // [7] exécution : l'arborescence des tâches crée une instance de CopyStorage lorsque l'on accède à root
        storage,
        QThreadFunctionTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneFlag::OnSuccess),
        QThreadFunctionTask<void>(onSaverSetup, onSaverDone, CallDoneFlag::OnSuccess)
    };
    return root;
}

...

const QString source =...;
const QString destination =...;
QTaskTree taskTree(copyRecipe(source, destination));
connect(&taskTree,&QTaskTree::done, &taskTree, [](DoneWith result) {
    if (result== DoneWith::Success)
        qDebug() << "The copying finished successfully.";
});
tasktree.start();

Dans l’exemple ci-dessus, les données inter-tâches sont constituées d’une variable de contenu QByteArray [2] encapsulée dans une structure personnalisée CopyStorage [1]. Si le chargeur s’exécute correctement, il stocke les données dans une variable CopyStorage::content [5]. Le programme de sauvegarde utilise ensuite cette variable pour configurer la tâche de sauvegarde [6].

Pour permettre à une arborescence de tâches de gérer la structure CopyStorage, une instance de Storage<CopyStorage> est créée [3]. Si une copie de cet objet est insérée en tant qu’élément enfant du groupe [7], une instance de la structure CopyStorage est créée dynamiquement lorsque l’arborescence de tâches entre dans ce groupe. Lorsque l’arborescence de tâches quitte ce groupe, l’instance existante de la structure CopyStorage est détruite car elle n’est plus nécessaire.

Si plusieurs arborescences de tâches contenant une copie de l’instance commune Storage<CopyStorage> s’exécutent simultanément (y compris lorsque ces arborescences s’exécutent dans des threads différents), chaque arborescence de tâches contient sa propre copie de la structure CopyStorage.

Vous pouvez accéder à ` CopyStorage ` depuis n’importe quel gestionnaire du groupe disposant d’un objet de stockage. Cela inclut tous les gestionnaires de toutes les tâches descendantes du groupe disposant d’un objet de stockage. Pour accéder à la structure personnalisée dans un gestionnaire, transmettez la copie de l’objet ` Storage<CopyStorage>` au gestionnaire (par exemple, dans une capture lambda) [4].

Lorsque l’arborescence des tâches invoque un gestionnaire dans une sous-arborescence contenant le stockage [7], l’arborescence des tâches active sa propre instance CopyStorage à l’intérieur de l’objet Storage<CopyStorage>. Par conséquent, la structure CopyStorage n’est accessible qu’à partir du corps du gestionnaire. Pour accéder à l’ CopyStorage actuellement active depuis Storage<CopyStorage>, utilisez la méthode Storage::operator->(), Storage::operator*() ou Storage::activeStorage().

La liste suivante résume comment utiliser un objet Storage dans l’arborescence des tâches :

  1. Définissez la structure personnalisée MyStorage avec des données personnalisées [1], [2]
  2. Créez une instance de l’objet Storage<MyStorage> (stockage) [3]
  3. Transmettez l'instance de l'Storage<MyStorage> aux gestionnaires [4]
  4. Accéder à l'instance « MyStorage » dans les gestionnaires [5], [6]
  5. Insérer l'instance Storage<MyStorage> dans un groupe [7]

Classe QTaskTree

QTaskTree exécute la structure arborescente des tâches asynchrones selon la recette décrite par l'élément racine Group.

Comme « QTaskTree » est également une tâche asynchrone, elle peut faire partie d’une autre « QTaskTree ». Pour placer une « QTaskTree » imbriquée à l’intérieur d’une autre « QTaskTree », insérez l’élément « QTaskTreeTask » dans un autre élément « Group ».

QTaskTree signale la progression des tâches terminées lors de son exécution. La valeur de progression est incrémentée lorsqu’une tâche se termine, est ignorée ou annulée. Lorsque QTaskTree est terminé et que le signal QTaskTree::done() est émis, la valeur actuelle de la progression est égale à la valeur maximale de progression. La progression maximale est égale au nombre total de tâches asynchrones dans une arborescence. Une « QTaskTree » imbriquée est comptée comme une seule tâche, et ses tâches enfants ne sont pas comptabilisées dans l’arborescence de niveau supérieur. Les groupes eux-mêmes ne sont pas comptés comme des tâches, mais leurs tâches le sont. Les tâches de type « QSyncTask » ne sont pas asynchrones ; elles ne sont donc pas comptées comme des tâches.

Pour définir des données initiales supplémentaires pour l’arborescence en cours d’exécution, modifiez les instances de stockage d’une arborescence lors de leur création en installant un gestionnaire de configuration de stockage :

Storage<CopyStorage> storage;
const Group root = ...; // storage placed inside root's group and inside handlers
QTaskTree taskTree(root);
auto initStorage = [](CopyStorage &storage) {
    storage.content = "initial content";
};
taskTree.onStorageSetup(storage, initStorage);
taskTree.start();

Lorsque l’arborescence des tâches en cours d’exécution crée une instance CopyStorage, et avant que tout gestionnaire au sein d’une arborescence ne soit appelé, l’arborescence des tâches appelle le gestionnaire initStorage, afin de permettre la configuration des données initiales du stockage, propres à cette exécution particulière de taskTree.

De même, pour collecter des données de résultats supplémentaires à partir de l’arborescence en cours d’exécution, lisez-les à partir des instances de stockage de l’arborescence au moment où elles sont sur le point d’être détruites. Pour ce faire, installez un gestionnaire de fin de stockage :

Storage<CopyStorage> storage;
const Group root =...; // stockage placé dans le groupe de root et dans les gestionnaires
QTaskTree taskTree(root);
auto collectStorage = [](const CopyStorage &storage) {
    qDebug() << "final content" << storage.content;
};
taskTree.onStorageDone(storage, collectStorage);
taskTree.start();

Lorsque l’arborescence de tâches en cours d’exécution s’apprête à détruire une instance de ` CopyStorage `, elle appelle le gestionnaire `collectStorage` afin de permettre la lecture des données finales provenant du stockage, propres à cette exécution particulière de ` taskTree`.

Adaptateurs de tâches

Il est très facile d’intégrer de nouveaux types de tâches dans les recettes. Il suffit de définir un nouvel alias de tâche pour le modèle ` QCustomTask `, en passant votre type ` Task ` comme premier argument du modèle, comme suit :

class Worker : public QObject
{
public:
    void start() { ... }

signals:
    void done(bool result);
};

using WorkerTask = QCustomTask<Worker>;

Cela fonctionnera si les conditions suivantes sont remplies :

  1. Votre tâche est dérivée de ` QObject`.
  2. Votre tâche dispose d’une méthode publique start() qui lance la tâche.
  3. Votre tâche émet un signal `done(bool)` ou `done(DoneResult)` lorsqu'elle est terminée.

Si votre tâche ne remplit pas ces conditions, vous pouvez tout de même l'adapter pour qu'elle fonctionne avec le framework TaskTree, en fournissant un deuxième argument de modèle avec l'adaptateur personnalisé. Imaginons que nous souhaitions adapter QTimer pour qu'il fonctionne avec TaskTree. L'Adapter pourrait ressembler à ceci :

class TimerAdapter
{
public:
    void operator()(QTimer *task, QTaskInterface *iface) {
        task->setSingleShot(true);
        QObject::connect(task, &QTimer::timeout, iface, [iface] {
            iface->reportDone(DoneResult::Success);
        });
        task->start();
    }
};

using TimerTask = QCustomTask<QTimer, TimerAdapter>;

Vous pouvez désormais commencer à utiliser l’ TimerTask dans vos recettes, comme suit :

const auto onSetup = [](QTimer&task) {
    task.setInterval(2000);
};

const auto onDone = [](const QTimer&task) {
    qDebug() << "Timer triggered after" << task.interval() << "ms.";
};

const Group recipe {
    TimerTask(onSetup, onDone)
};

Remarque : la classe implémentant la tâche en cours d’exécution doit disposer d’un constructeur par défaut, et les objets de cette classe doivent pouvoir être librement détruits. Il doit être possible de détruire une tâche en cours d’exécution, de préférence sans attendre qu’elle se termine (c’est-à-dire un destructeur sûr et non bloquant pour une tâche en cours d’exécution). Pour obtenir une destruction non bloquante d’une tâche dotée d’un destructeur bloquant, envisagez d’utiliser le paramètre de modèle facultatif ` Deleter ` de la classe ` QCustomTask ` (le troisième argument de modèle).

Exécuteurs d’arbres de tâches

Le gestionnaire d’arborescence de tâches gère le cycle de vie de l’ QTaskTree sous-jacente utilisée pour exécuter la recette donnée.

Le tableau suivant résume les différences entre les différents exécutants d'arborescence de tâches :

Nom de la classeDescription
QSingleTaskTreeRunnerGère l’exécution d’un seul arbre de tâches. La méthode QSingleTaskTreeRunner::start() lance sans condition la recette transmise, en réinitialisant tout arbre de tâches éventuellement en cours d’exécution. Un seul arbre de tâches peut s’exécuter à la fois.
QSequentialTaskTreeRunnerGère l’exécution séquentielle des arbres de tâches. La méthode ` QSequentialTaskTreeRunner::enqueue()` lance la recette transmise si le gestionnaire d’arbres de tâches est inactif. Sinon, la recette est mise en file d’attente. Lorsque la tâche en cours est terminée, le gestionnaire exécute la recette retirée de la file d’attente de manière séquentielle. Un seul arbre de tâches peut être en cours d’exécution à la fois.
QParallelTaskTreeRunnerGère les exécutions parallèles d’arbres de tâches. La méthode QParallelTaskTreeRunner::start() lance sans condition la recette transmise et maintient en parallèle les éventuels arbres de tâches en cours d’exécution.
QMappedTaskTreeRunnerGère les exécutions d'arbres de tâches mappés. La méthode ` QMappedTaskTreeRunner::start()` lance sans condition la recette spécifiée pour une clé donnée. Si un autre arbre de tâches portant la même clé est déjà en cours d'exécution, celui-ci sera réinitialisé. Les arbres de tâches portant des clés différentes ne sont pas affectés et poursuivent leur exécution.

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