aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJarek Kobus <jaroslaw.kobus@qt.io>2023-02-01 14:16:18 +0100
committerJarek Kobus <jaroslaw.kobus@qt.io>2023-02-14 16:00:41 +0000
commit0f23dd99b940d9526a18aaa4866d5d0b6d78f62f (patch)
tree3e50e2a70e6ec51a10fed3419e200492b8543303
parentec0b5ef92ad32bc43fdbe55d24d704066e9065af (diff)
TaskTree: Add more docs
Change-Id: Ie87ff78220d35266cc6e23ade7a1b4df4f483218 Reviewed-by: Leena Miettinen <riitta-leena.miettinen@qt.io> Reviewed-by: Eike Ziller <eike.ziller@qt.io>
-rw-r--r--src/libs/utils/tasktree.cpp745
1 files changed, 707 insertions, 38 deletions
diff --git a/src/libs/utils/tasktree.cpp b/src/libs/utils/tasktree.cpp
index 1cd4fd3f01..f99b20d761 100644
--- a/src/libs/utils/tasktree.cpp
+++ b/src/libs/utils/tasktree.cpp
@@ -589,46 +589,715 @@ void TaskNode::invokeEndHandler(bool success)
/*!
\class Utils::TaskTree
-
- \brief The TaskTree class is responsible for running async task tree structure defined in a
+ \inheaderfile utils/tasktree.h
+ \inmodule QtCreator
+ \ingroup mainclasses
+ \brief The TaskTree class runs an async task tree structure defined in a
declarative way.
- The Tasking namespace (similar to Layouting) is designer for building declarative task
- tree structure. The examples of tasks that can be used inside TaskTree are e.g. QtcProcess,
- FileTransfer, AsyncTask<>. It's extensible, so any possible asynchronous task may be
- integrated and used inside TaskTree. TaskTree enables to form sophisticated mixtures of
- parallel or sequential flow of tasks in tree form.
-
- The TaskTree consist of Group root element. The Group can have nested Group elements.
- The Group may also contain any number of tasks, e.g. Process, FileTransfer,
- AsyncTask<ReturnType>.
-
- Each Group can contain various other elements describing the processing flow.
-
- The execute mode elements of a Group specify how direct children of a Group will be executed.
- The "sequential" element of a Group means all tasks in a group will be executed in chain,
- so after the previous task finished, the next will be started. This is the default Group
- behavior. The "parallel" element of a Group means that all tasks in a Group will be started
- simultaneously. When having nested Groups hierarchy, we may mix execute modes freely
- and each Group will be executed according to its own execute mode.
- The "sequential" mode may be very useful in cases when result data from one task is need to
- be passed as an input data to the other task - sequential mode guarantees that the next
- task will be started only after the previous task has already finished.
-
- There are many possible "workflow" behaviors for the Group. E.g. "stopOnError",
- the default Group workflow behavior, means that whenever any direct child of a Group
- finished with error, we immediately stop processing other tasks in this group
- (in parallel case) by canceling them and immediately finish the Group with error.
-
- The user of TaskTree specifies how to setup his tasks (by providing TaskSetupHandlers)
- and how to collect output data from the finished tasks (by providing TaskEndHandlers).
- The user don't need to create tasks manually - TaskTree will create them when it's needed
- and destroy when they are not used anymore.
-
- Whenever a Group elemenent is being started, the Group's OnGroupSetup handler is being called.
- Just after the handler finishes, all Group's children are executed (either in parallel or
- in sequence). When all Group's children finished, one of Group's OnGroupDone or OnGroupError
- is being executed, depending on results of children execution and Group's workflow policy.
+ Use the Tasking namespace to build extensible, declarative task tree
+ structures that contain possibly asynchronous tasks, such as QtcProcess,
+ FileTransfer, or AsyncTask<ReturnType>. TaskTree structures enable you
+ to create a sophisticated mixture of a parallel or sequential flow of tasks
+ in the form of a tree and to run it any time later.
+
+ \section1 Root Element and Tasks
+
+ The TaskTree has a mandatory Group root element, which may contain
+ any number of tasks of various types, such as Process, FileTransfer,
+ or AsyncTask<ReturnType>:
+
+ \code
+ using namespace Utils;
+ using namespace Tasking;
+
+ const Group root {
+ Process(...),
+ Async<int>(...),
+ Transfer(...)
+ };
+
+ TaskTree *taskTree = new TaskTree(root);
+ connect(taskTree, &TaskTree::done, ...); // a successfully finished handler
+ connect(taskTree, &TaskTree::errorOccurred, ...); // an erroneously finished handler
+ taskTree->start();
+ \endcode
+
+ The task tree above has a top level element of the Group type that contains
+ tasks of the type QtcProcess, FileTransfer, and AsyncTask<int>.
+ After taskTree->start() is called, the tasks are run in a chain, starting
+ with Process. When Process finishes successfully, the Async<int> task is
+ started. Finally, when the asynchronous task finishes successfully, the
+ FileTransfer task is started.
+
+ When the last running task finishes with success, the task tree is considered
+ to have run successfully and the TaskTree::done() signal is emitted.
+ When a task finishes with an error, the execution of the task tree is stopped
+ and the remaining tasks are skipped. The task tree finishes with an error and
+ sends the TaskTree::errorOccurred() signal.
+
+ \section1 Groups
+
+ The parent of the Group sees it as a single task. Like other tasks,
+ the group can be started and it can finish with success or an error.
+ The Group elements can be nested to create a tree structure:
+
+ \code
+ const Group root {
+ Group {
+ parallel,
+ Process(...),
+ Async<int>(...)
+ },
+ Transfer(...)
+ };
+ \endcode
+
+ The example above differs from the first example in that the root element has
+ a subgroup that contains the Process and Async<int> tasks. The subgroup is a
+ sibling element of the Transfer task in the root. The subgroup contains an
+ additional \e parallel element that instructs its Group to execute its tasks
+ in parallel.
+
+ So, when the tree above is started, the Process and Async<int> tasks start
+ immediately and run in parallel. Since the root group doesn't contain a
+ \e parallel element, its direct child tasks are run in sequence. Thus, the
+ Transfer task starts when the whole subgroup finishes. The group is
+ considered as finished when all its tasks have finished. The order in which
+ the tasks finish is not relevant.
+
+ So, depending on which task lasts longer (Process or Async<int>), the
+ following scenarios can take place:
+
+ \table
+ \header
+ \li Scenario 1
+ \li Scenario 2
+ \row
+ \li Root Group starts
+ \li Root Group starts
+ \row
+ \li Sub Group starts
+ \li Sub Group starts
+ \row
+ \li Process starts
+ \li Process starts
+ \row
+ \li Async<int> starts
+ \li Async<int> starts
+ \row
+ \li ...
+ \li ...
+ \row
+ \li \b {Process finishes}
+ \li \b {Async<int> finishes}
+ \row
+ \li ...
+ \li ...
+ \row
+ \li \b {Async<int> finishes}
+ \li \b {Process finishes}
+ \row
+ \li Sub Group finishes
+ \li Sub Group finishes
+ \row
+ \li Transfer starts
+ \li Transfer starts
+ \row
+ \li ...
+ \li ...
+ \row
+ \li Transfer finishes
+ \li Transfer finishes
+ \row
+ \li Root Group finishes
+ \li Root Group finishes
+ \endtable
+
+ The differences between the scenarios are marked with bold. Three dots mean
+ that an unspecified amount of time passes between previous and next events
+ (a task or tasks continue to run). No dots between events
+ means that they occur synchronously.
+
+ The presented scenarios assume that all tasks run successfully. If a task
+ fails during execution, the task tree finishes with an error. In particular,
+ when Process finishes with an error while Async<int> is still being executed,
+ Async<int> is automatically stopped, the subgroup finishes with an error,
+ Transfer is skipped, and the tree finishes with an error.
+
+ \section1 Task Types
+
+ Each task type is associated with its corresponding task class that executes
+ the task. For example, a Process task inside a task tree is associated with
+ the QtcProcess class that executes the process. The associated objects are
+ automatically created, started, and destructed exclusively by the task tree
+ at the appropriate time.
+
+ If a root group consists of five sequential Process tasks, and the task tree
+ executes the group, it creates an instance of QtcProcess for the first
+ Process task and starts it. If the QtcProcess instance finishes successfully,
+ the task tree destructs it and creates a new QtcProcess instance for the
+ second Process, and so on. If the first task finishes with an error, the task
+ tree stops creating QtcProcess instances, and the root group finishes with an
+ error.
+
+ The following table shows examples of task types and their corresponding task
+ classes:
+
+ \table
+ \header
+ \li Task Type (Tasking Namespace)
+ \li Associated Task Class
+ \li Brief Description
+ \row
+ \li Process
+ \li Utils::QtcProcess
+ \li Starts processes.
+ \row
+ \li Async<ReturnType>
+ \li Utils::AsyncTask<ReturnType>
+ \li Starts asynchronous tasks; run in separate thread.
+ \row
+ \li Tree
+ \li Utils::TaskTree
+ \li Starts a nested task tree.
+ \row
+ \li Transfer
+ \li ProjectExplorer::FileTransfer
+ \li Starts file transfer between different devices.
+ \endtable
+
+ \section1 Task Handlers
+
+ Use Task handlers to set up a task for execution and to enable reading
+ the output data from the task when it finishes with success or an error.
+
+ \section2 Task Start Handler
+
+ When a corresponding task class object is created and before it's started,
+ the task tree invokes a mandatory user-provided setup handler. The setup
+ handler should always take a \e reference to the associated task class object:
+
+ \code
+ const auto onSetup = [](QtcProcess &process) {
+ process.setCommand({"sleep", {"3"}});
+ };
+ const Group root {
+ Process(onSetup)
+ };
+ \endcode
+
+ You can modify the passed QtcProcess in the setup handler, so that the task
+ tree can start the process according to your configuration.
+ You do not need to call \e {process.start();} in the setup handler,
+ as the task tree calls it when needed. The setup handler is mandatory
+ and must be the first argument of the task's constructor.
+
+ Optionally, the setup handler may return a TaskAction. The returned
+ TaskAction influences the further start behavior of a given task. The
+ possible values are:
+
+ \table
+ \header
+ \li TaskAction Value
+ \li Brief Description
+ \row
+ \li Continue
+ \li The task is started normally. This is the default behavior when the
+ setup handler doesn't return TaskAction (that is, its return type is
+ void).
+ \row
+ \li StopWithDone
+ \li The task won't be started and it will report success to its parent.
+ \row
+ \li StopWithError
+ \li The task won't be started and it will report an error to its parent.
+ \endtable
+
+ This is useful for running a task only when a condition is met and the data
+ needed to evaluate this condition is not known until previously started tasks
+ finish. This way, the setup handler dynamically decides whether to start the
+ corresponding task normally or skip it and report success or an error.
+ For more information about inter-task data exchange, see \l Storage.
+
+ \section2 Task's Done and Error Handlers
+
+ When a running task finishes, the task tree invokes an optionally provided
+ done or error handler. Both handlers should always take a \e {const reference}
+ to the associated task class object:
+
+ \code
+ const auto onSetup = [](QtcProcess &process) {
+ process.setCommand({"sleep", {"3"}});
+ };
+ const auto onDone = [](const QtcProcess &process) {
+ qDebug() << "Success" << process.cleanedStdOut();
+ };
+ const auto onError = [](const QtcProcess &process) {
+ qDebug() << "Failure" << process.cleanedStdErr();
+ };
+ const Group root {
+ Process(onSetup, onDone, onError)
+ };
+ \endcode
+
+ The done and error handlers may collect output data from QtcProcess, and store it
+ for further processing or perform additional actions. The done handler is optional.
+ When used, it must be the second argument of the task constructor.
+ The error handler must always be the third argument.
+ You can omit the handlers or substitute the ones that you do not need with curly braces ({}).
+
+ \note If the task setup handler returns StopWithDone or StopWithError,
+ neither the done nor error handler is invoked.
+
+ \section1 Group Handlers
+
+ Similarly to task handlers, group handlers enable you to set up a group to
+ execute and to apply more actions when the whole group finishes with
+ success or an error.
+
+ \section2 Group's Start Handler
+
+ The task tree invokes the group start handler before it starts the child
+ tasks. The group handler doesn't take any arguments:
+
+ \code
+ const auto onGroupSetup = [] {
+ qDebug() << "Entering the group";
+ };
+ const Group root {
+ OnGroupSetup(onGroupSetup),
+ Process(...)
+ };
+ \endcode
+
+ The group setup handler is optional. To define a group setup handler, add an
+ OnGroupSetup element to a group. The argument of OnGroupSetup is a user
+ handler. If you add more than one OnGroupSetup element to a group, an assert
+ is triggered at runtime that includes an error message.
+
+ Like the task start handler, the group start handler may return TaskAction.
+ The returned TaskAction value affects the start behavior of the
+ whole group. If you do not specify a group start handler or its return type
+ is void, the default group's action is TaskAction::Continue, so that all
+ tasks are started normally. Otherwise, when the start handler returns
+ TaskAction::StopWithDone or TaskAction::StopWithError, the tasks are not
+ started (they are skipped) and the group itself reports success or failure,
+ depending on the returned value, respectively.
+
+ \code
+ const Group root {
+ OnGroupSetup([] { qDebug() << "Root setup"; }),
+ Group {
+ OnGroupSetup([] { qDebug() << "Group 1 setup"; return TaskAction::Continue; }),
+ Process(...) // Process 1
+ },
+ Group {
+ OnGroupSetup([] { qDebug() << "Group 2 setup"; return TaskAction::StopWithDone; }),
+ Process(...) // Process 2
+ },
+ Group {
+ OnGroupSetup([] { qDebug() << "Group 3 setup"; return TaskAction::StopWithError; }),
+ Process(...) // Process 3
+ },
+ Process(...) // Process 4
+ };
+ \endcode
+
+ In the above example, all subgroups of a root group define their setup handlers.
+ The following scenario assumes that all started processes finish with success:
+
+ \table
+ \header
+ \li Scenario
+ \li Comment
+ \row
+ \li Root Group starts
+ \li Doesn't return TaskAction, so its tasks are executed.
+ \row
+ \li Group 1 starts
+ \li Returns Continue, so its tasks are executed.
+ \row
+ \li Process 1 starts
+ \li
+ \row
+ \li ...
+ \li ...
+ \row
+ \li Process 1 finishes (success)
+ \li
+ \row
+ \li Group 1 finishes (success)
+ \li
+ \row
+ \li Group 2 starts
+ \li Returns StopWithDone, so Process 2 is skipped and Group 2 reports
+ success.
+ \row
+ \li Group 2 finishes (success)
+ \li
+ \row
+ \li Group 3 starts
+ \li Returns StopWithError, so Process 3 is skipped and Group 3 reports
+ an error.
+ \row
+ \li Group 3 finishes (error)
+ \li
+ \row
+ \li Root Group finishes (error)
+ \li Group 3, which is a direct child of the root group, finished with an
+ error, so the root group stops executing, skips Process 4, which has
+ not started yet, and reports an error.
+ \endtable
+
+ \section2 Groups's Done and Error Handlers
+
+ A Group's done or error handler is executed after the successful or failed
+ execution of its tasks, respectively. The final value reported by the
+ group depends on its \l {Workflow Policy}. The handlers can apply other
+ necessary actions. The done and error handlers are defined inside the
+ OnGroupDone and OnGroupError elements of a group, respectively. They do not
+ take arguments:
+
+ \code
+ const Group root {
+ OnGroupSetup([] { qDebug() << "Root setup"; }),
+ Process(...),
+ OnGroupDone([] { qDebug() << "Root finished with success"; }),
+ OnGroupError([] { qDebug() << "Root finished with error"; })
+ };
+ \endcode
+
+ The group done and error handlers are optional. If you add more than one
+ OnGroupDone or OnGroupError each to a group, an assert is triggered at
+ runtime that includes an error message.
+
+ \note Even if the group setup handler returns StopWithDone or StopWithError,
+ one of the task's done or error handlers is invoked. This behavior differs
+ from that of task handlers and might change in the future.
+
+ \section1 Other Group Elements
+
+ A group can contain other elements that describe the processing flow, such as
+ the execution mode or workflow policy. It can also contain storage elements
+ that are responsible for collecting and sharing custom common data gathered
+ during group execution.
+
+ \section2 Execution Mode
+
+ The execution mode element in a Group specifies how the direct child tasks of
+ the Group are started.
+
+ \table
+ \header
+ \li Execution Mode
+ \li Description
+ \row
+ \li sequential
+ \li Default. When a Group has no execution mode, it runs in the
+ sequential mode. All the direct child tasks of a group are started
+ in a chain, so that when one task finishes, the next one starts.
+ This enables you to pass the results from the previous task
+ as input to the next task before it starts. This mode guarantees
+ that the next task is started only after the previous task finishes.
+ \row
+ \li parallel
+ \li All the direct child tasks of a group are started after the group is
+ started, without waiting for the previous tasks to finish. In this
+ mode, all tasks run simultaneously.
+ \row
+ \li ParallelLimit(int limit)
+ \li In this mode, a limited number of direct child tasks run simultaneously.
+ The \e limit defines the maximum number of tasks running in parallel
+ in a group. When the group is started, the first batch tasks is
+ started (the number of tasks in batch equals to passed limit, at most),
+ while the others are kept waiting. When a running task finishes,
+ the group starts the next remaining one, so that the \e limit
+ of simultaneously running tasks inside a group isn't exceeded.
+ This repeats on every child task's finish until all child tasks are started.
+ This enables you to limit the maximum number of tasks that
+ run simultaneously, for example if running too many processes might
+ block the machine for a long time. The value 1 means \e sequential
+ execution. The value 0 means unlimited and equals \e parallel.
+ \endtable
+
+ In all execution modes, a group starts tasks in the oder in which they appear.
+
+ If a child of a group is also a group (in a nested tree), the child group
+ runs its tasks according to its own execution mode.
+
+ \section2 Workflow Policy
+
+ The workflow policy element in a Group specifies how the group should behave
+ when its direct child tasks finish:
+
+ \table
+ \header
+ \li Workflow Policy
+ \li Description
+ \row
+ \li stopOnError
+ \li Default. If a task finishes with an error, the group:
+ \list 1
+ \li Stops the running tasks (if any - for example, in parallel
+ mode).
+ \li Skips executing tasks it has not started (for example, in the
+ sequential mode).
+ \li Immediately finishes with an error.
+ \endlist
+ If all child tasks finish successfully or the group is empty, the group
+ finishes with success.
+ \row
+ \li continueOnError
+ \li Similar to stopOnError, but in case any child finishes with
+ an error, the execution continues until all tasks finish,
+ and the group reports an error afterwards, even when some other
+ tasks in group finished with success.
+ If a task finishes with an error, the group:
+ \list 1
+ \li Continues executing the tasks that are running or have not
+ started yet.
+ \li Finishes with an error when all tasks finish.
+ \endlist
+ If all tasks finish successfully or the group is empty, the group
+ finishes with success.
+ \row
+ \li stopOnDone
+ \li If a task finishes with success, the group:
+ \list 1
+ \li Stops running tasks and skips those that it has not started.
+ \li Immediately finishes with success.
+ \endlist
+ If all tasks finish with an error or the group is empty, the group
+ finishes with an error.
+ \row
+ \li continueOnDone
+ \li Similar to stopOnDone, but in case any child finishes
+ successfully, the execution continues until all tasks finish,
+ and the group reports success afterwards, even when some other
+ tasks in group finished with an error.
+ If a task finishes with success, the group:
+ \list 1
+ \li Continues executing the tasks that are running or have not
+ started yet.
+ \li Finishes with success when all tasks finish.
+ \endlist
+ If all tasks finish with an error or the group is empty, the group
+ finishes with an error.
+ \row
+ \li optional
+ \li The group executes all tasks and ignores their return state. If all
+ tasks finish or the group is empty, the group finishes with success.
+ \endtable
+
+ If a child of a group is also a group (in a nested tree), the child group
+ runs its tasks according to its own workflow policy.
+
+ \section2 Storage
+
+ Use the Storage element to exchange information between tasks. Especially,
+ in the sequential execution mode, when a task needs data from another task
+ before it can start. For example, a task tree that copies data by reading
+ it from a source and writing it to a destination might look as follows:
+
+ \code
+ static QByteArray load(const FilePath &fileName) { ... }
+ static void save(const FilePath &fileName, const QByteArray &array) { ... }
+
+ static TaskItem diffRecipe(const FilePath &source, const FilePath &destination)
+ {
+ struct CopyStorage { // [1] custom inter-task struct
+ QByteArray content; // [2] custom inter-task data
+ };
+
+ // [3] instance of custom inter-task struct manageable by task tree
+ const TreeStorage<CopyStorage> storage;
+
+ const auto onLoaderSetup = [source](Async<QByteArray> &async) {
+ async.setAsyncCallData(&load, source);
+ };
+ // [4] runtime: task tree activates the instance from [5] before invoking handler
+ const auto onLoaderDone = [storage](const Async<QByteArray> &async) {
+ storage->content = async.result();
+ };
+
+ // [4] runtime: task tree activates the instance from [5] before invoking handler
+ const auto onSaverSetup = [storage, destination](Async<void> &async) {
+ async.setAsyncCallData(&save, destination, storage->content);
+ };
+ const auto onSaverDone = [](const Async<void> &async) {
+ qDebug() << "Save done successfully";
+ };
+
+ const Group root {
+ // [5] runtime: task tree creates an instance of CopyStorage when root is entered
+ Storage(storage),
+ Async<QByteArray>(onLoaderSetup, onLoaderDone),
+ Async<void>(onSaverSetup, onSaverDone)
+ };
+ return root;
+ }
+ \endcode
+
+ In the example above, the inter-task data consists of a QByteArray content
+ variable [2] enclosed in a CopyStorage custom struct [1]. If the loader
+ finishes successfully, it stores the data in a CopyStorage::content
+ variable. The saver then uses the variable to configure the saving task.
+
+ To enable a task tree to manage the CopyStorage struct, an instance of
+ TreeStorage<CopyStorage> is created [3]. If a copy of this object is
+ inserted as group's child task [5], an instance of CopyStorage struct is
+ created dynamically when the task tree enters this group. When the task
+ tree leaves this group, the existing instance of CopyStorage struct is
+ destructed as it's no longer needed.
+
+ If several task trees that hold a copy of the common TreeStorage<CopyStorage>
+ instance run simultaneously, each task tree contains its own copy of the
+ CopyStorage struct.
+
+ You can access CopyStorage from any handler in the group with a storage object.
+ This includes all handlers of all descendant tasks of the group with
+ a storage object. To access the custom struct in a handler, pass the
+ copy of the TreeStorage<CopyStorage> object to the handler (for example, in
+ a lambda capture) [4].
+
+ When the task tree invokes a handler in a subtree containing the storage [5],
+ the task tree activates its own CopyStorage instance inside the
+ TreeStorage<CopyStorage> object. Therefore, the CopyStorage struct may be
+ accessed only from within the handler body. To access the currently active
+ CopyStorage from within TreeStorage<CopyStorage>, use the TreeStorage::operator->()
+ or TreeStorage::activeStorage() method.
+
+ The following list summarizes how to employ a Storage object into the task
+ tree:
+ \list 1
+ \li Define the custom structure MyStorage with custom data [1], [2]
+ \li Create an instance of TreeStorage<MyStorage> storage [3]
+ \li Pass the TreeStorage<MyStorage> instance to handlers [4]
+ \li Insert the TreeStorage<MyStorage> instance into a group [5]
+ \endlist
+
+ \note The current implementation assumes that all running task trees
+ containing copies of the same TreeStorage run in the same thread. Otherwise,
+ the behavior is undefined.
+
+ \section1 TaskTree
+
+ TaskTree executes the tree structure of asynchronous tasks according to the
+ recipe described by the Group root element.
+
+ As TaskTree is also an asynchronous task, it can be a part of another TaskTree.
+ To place a nested TaskTree inside another TaskTree, insert the Tasking::Tree
+ element into other tree's Group element.
+
+ TaskTree reports progress of completed tasks when running. The progress value
+ is increased when a task finishes or is skipped or stopped.
+ When TaskTree is finished and the TaskTree::done() or TaskTree::errorOccurred()
+ signal is emitted, the current value of the progress equals the maximum
+ progress value. Maximum progress equals the total number of tasks in a tree.
+ A nested TaskTree is counted as a single task, and its child tasks are not
+ counted in the top level tree. Groups themselves are not counted as tasks,
+ but their tasks are counted.
+
+ To set additional initial data for the running tree, modify the storage
+ instances in a tree when it creates them by installing a storage setup
+ handler:
+
+ \code
+ TreeStorage<CopyStorage> storage;
+ Group root = ...; // storage placed inside root's group and inside handlers
+ TaskTree taskTree(root);
+ auto initStorage = [](CopyStorage *storage){
+ storage->content = "initial content";
+ };
+ taskTree.onStorageSetup(storage, initStorage);
+ taskTree.start();
+ \endcode
+
+ When the running task tree creates a CopyStorage instance, and before any
+ handler inside a tree is called, the task tree calls the initStorage handler,
+ to enable setting up initial data of the storage, unique to this particular
+ run of taskTree.
+
+ Similarly, to collect some additional result data from the running tree,
+ read it from storage instances in the tree when they are about to be
+ destroyed. To do this, install a storage done handler:
+
+ \code
+ TreeStorage<CopyStorage> storage;
+ Group root = ...; // storage placed inside root's group and inside handlers
+ TaskTree taskTree(root);
+ auto collectStorage = [](CopyStorage *storage){
+ qDebug() << "final content" << storage->content;
+ };
+ taskTree.onStorageDone(storage, collectStorage);
+ taskTree.start();
+ \endcode
+
+ When the running task tree is about to destroy a CopyStorage instance, the
+ task tree calls the collectStorage handler, to enable reading the final data
+ from the storage, unique to this particular run of taskTree.
+
+ \section1 Task Adapters
+
+ To extend a TaskTree with new a task type, implement a simple adapter class
+ derived from the TaskAdapter class template. The following class is an
+ adapter for a single shot timer, which may be considered as a new
+ asynchronous task:
+
+ \code
+ class TimeoutAdapter : public Utils::Tasking::TaskAdapter<QTimer>
+ {
+ public:
+ TimeoutAdapter() {
+ task()->setSingleShot(true);
+ task()->setInterval(1000);
+ connect(task(), &QTimer::timeout, this, [this] { emit done(true); });
+ }
+ void start() final { task()->start(); }
+ };
+
+ QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter);
+ \endcode
+
+ You must derive the custom adapter from the TaskAdapter class template
+ instantiated with a template parameter of the class implementing a running
+ task. The code above uses QTimer to run the task. This class appears
+ later as an argument to the task's handlers. The instance of this class
+ parameter automatically becomes a member of the TaskAdapter template, and is
+ accessible through the TaskAdapter::task() method. The constructor
+ of TimeoutAdapter initially configures the QTimer object and connects
+ to the QTimer::timeout signal. When the signal is triggered, TimeoutAdapter
+ emits the done(true) signal to inform the task tree that the task finished
+ successfully. If it emits done(false), the task finished with an error.
+ The TaskAdapter::start() method starts the timer.
+
+ To make QTimer accessible inside TaskTree under the \e Timeout name,
+ register it with QTC_DECLARE_CUSTOM_TASK(Timeout, TimeoutAdapter). Timeout
+ becomes a new task type inside Utils::Tasking namespace, using TimeoutAdapter.
+
+ The new task type is now registered, and you can use it in TaskTree:
+
+ \code
+ const auto onTimeoutSetup = [](QTimer &task) {
+ task.setInterval(2000);
+ };
+ const auto onTimeoutDone = [](const QTimer &task) {
+ qDebug() << "timeout triggered";
+ };
+
+ const Group root {
+ Timeout(onTimeoutSetup, onTimeoutDone)
+ };
+ \endcode
+
+ When a task tree containing the root from the above example is started, it
+ prints a debug message within two seconds and then finishes successfully.
+
+ \note The class implementing the running task should have a default constructor,
+ and objects of this class should be freely destructible. It should be allowed
+ to destroy a running object, preferably without waiting for the running task
+ to finish (that is, safe non-blocking destructor of a running task).
*/
TaskTree::TaskTree()