From 152d543cdf4197372c6e1d59c6d60e9b8674fe56 Mon Sep 17 00:00:00 2001 From: Jarek Kobus Date: Tue, 20 Jun 2023 18:54:38 +0200 Subject: [PATCH] TaskTree: Document GroupItem and subclasses Adapt some type and argument names accordingly. Change-Id: I1b127eacfd0a71574ee9e9a7628ec572620798cb Reviewed-by: hjk --- src/libs/solutions/tasking/barrier.h | 2 +- src/libs/solutions/tasking/tasktree.cpp | 370 +++++++++++++++++++++++- src/libs/solutions/tasking/tasktree.h | 24 +- 3 files changed, 376 insertions(+), 20 deletions(-) diff --git a/src/libs/solutions/tasking/barrier.h b/src/libs/solutions/tasking/barrier.h index 705ddd5f5b7..64a59ec8733 100644 --- a/src/libs/solutions/tasking/barrier.h +++ b/src/libs/solutions/tasking/barrier.h @@ -82,7 +82,7 @@ public: "is not reachable in the running tree. " "It is possible that no barrier was added to the tree, " "or the storage is not reachable from where it is referenced. " - "The WaitForBarrier task will finish with error. "); + "The WaitForBarrier task finishes with an error. "); return SetupResult::StopWithError; } Barrier *activeSharedBarrier = activeBarrier->barrier(); diff --git a/src/libs/solutions/tasking/tasktree.cpp b/src/libs/solutions/tasking/tasktree.cpp index b9417a6e46b..ff43b3a6129 100644 --- a/src/libs/solutions/tasking/tasktree.cpp +++ b/src/libs/solutions/tasking/tasktree.cpp @@ -138,6 +138,369 @@ private: Returns the const pointer to the associated \c Task instance. */ +/*! + \class Tasking::GroupItem + \inheaderfile solutions/tasking/tasktree.h + \inmodule TaskingSolution + \brief GroupItem represents the basic element that may be a part of any + \l {Tasking::Group} {Group}. + + GroupItem is a basic element that may be a part of any \l {Tasking::Group} {Group}. + It encapsulates the functionality provided by any GroupItem's subclass. + It is a value type and it is safe to copy the GroupItem instance, + even when it is originally created via the subclass' constructor. + + There are four main kinds of GroupItem: + \table + \header + \li GroupItem Kind + \li Brief Description + \row + \li \l CustomTask + \li Defines asynchronous task type and task's start, done, and error handlers. + Aliased with a unique task name, such as, \c ConcurrentCallTask + or \l NetworkQueryTask. Asynchronous tasks are the main reason for using a task tree. + \row + \li \l Group + \li A container for other group items. Since the group is of the GroupItem type, + it's possible to nest it inside another group. The group is seen by its parent + as a single asynchronous task. + \row + \li \l Storage + \li Enables the child tasks of a group to exchange data. + When Storage is placed inside a group, the task tree instantiates + the storage object just before the group is entered, + and destroys it just after the group is finished. + \row + \li Other group control items + \li The items returned by \l {Tasking::parallelLimit()} {parallelLimit()} or + \l {Tasking::workflowPolicy()} {workflowPolicy()} influence the group's behavior. + The items returned by \l {Tasking::onGroupSetup()} {onGroupSetup()}, + \l {Tasking::onGroupDone()} {onGroupDone()} or + \l {Tasking::onGroupError()} {onGroupError()} define custom handlers called when + the group starts or ends execution. + \endtable +*/ + +/*! + \class Tasking::Group + \inheaderfile solutions/tasking/tasktree.h + \inmodule TaskingSolution + \brief Group represents the basic element for composing declarative recipes describing + how to execute and handle a nested tree of asynchronous tasks. + + Group is a container for other group items. It encloses child tasks into one unit, + which is seen by the group's parent as a single, asynchronous task. + Since Group is of the GroupItem type, it may also be a child of Group. + + Insert child tasks into the group by using aliased custom task names, such as, + \c ConcurrentCallTask or \c NetworkQueryTask: + + \code + const Group group { + NetworkQueryTask(...), + ConcurrentCallTask(...) + }; + \endcode + + The group's behavior may be customized by inserting the items returned by + \l {Tasking::parallelLimit()} {parallelLimit()} or + \l {Tasking::workflowPolicy()} {workflowPolicy()} functions: + + \code + const Group group { + parallel, + continueOnError, + NetworkQueryTask(...), + NetworkQueryTask(...) + }; + \endcode + + The group may contain nested groups: + + \code + const Group group { + finishAllAndDone, + NetworkQueryTask(...), + Group { + NetworkQueryTask(...), + Group { + parallel, + NetworkQueryTask(...), + NetworkQueryTask(...), + } + ConcurrentCallTask(...) + } + }; + \endcode + + The group may dynamically instantiate a custom storage structure, which may be used for + inter-task data exchange: + + \code + struct MyCustomStruct { QByteArray data; }; + + TreeStorage storage; + + const auto onFirstSetup = [](NetworkQuery &task) { ... }; + const auto onFirstDone = [storage](const NetworkQuery &task) { + // storage-> gives a pointer to MyCustomStruct instance, + // created dynamically by the running task tree. + storage->data = task.reply()->readAll(); + }; + const auto onSecondSetup = [storage](ConcurrentCall &task) { + // storage-> gives a pointer to MyCustomStruct. Since the group is sequential, + // the stored MyCustomStruct was already updated inside the onFirstDone handler. + const QByteArray storedData = storage->data; + }; + + const Group group { + // When the group is entered by a running task tree, it creates MyCustomStruct + // instance dynamically. It is later accessible from all handlers via + // the *storage or storage-> operators. + sequential, + Storage(storage), + NetworkQueryTask(onFirstSetup, onFirstDone), + ConcurrentCallTask(onSecondSetup) + }; + \endcode +*/ + +/*! + \fn Group::Group(const QList &children) + + Constructs a group with a given list of \a children. + + This constructor is useful when the child items of the group are not known at compile time, + but later, at runtime: + + \code + const QStringList sourceList = ...; + + QList groupItems { parallel }; + + for (const QString &source : sourceList) { + const NetworkQueryTask task(...); // use source for setup handler + groupItems << task; + } + + const Group group(groupItems); + \endcode +*/ + +/*! + \fn Group::Group(std::initializer_list children) + + Constructs a group from std::initializer_list given by \a children. + + This constructor is useful when all child items of the group are known at compile time: + + \code + const Group group { + finishAllAndDone, + NetworkQueryTask(...), + Group { + NetworkQueryTask(...), + Group { + parallel, + NetworkQueryTask(...), + NetworkQueryTask(...), + } + ConcurrentCallTask(...) + } + }; + \endcode +*/ + +/*! + \fn GroupItem Group::withTimeout(std::chrono::milliseconds timeout, const GroupEndHandler &handler) const + + Attaches \c TimeoutTask to a copy of \c this group, elapsing after \a timeout in milliseconds, + with an optionally provided timeout \a handler, and returns the coupled item. + + When the group finishes before \a timeout passes, + the returned item finishes immediately with the group's result. + Otherwise, the \a handler is invoked (if provided), the group is stopped, + and the returned item finishes with an error. +*/ + +/*! + \class Tasking::CustomTask + \inheaderfile solutions/tasking/tasktree.h + \inmodule TaskingSolution + \brief A class template used for declaring task items and defining their setup, + done, and error handlers. + + The CustomTask class template is used inside TaskTree for describing custom task items. + + Custom task names are aliased with unique names inside the \l Tasking namespace + via the TASKING_DECLARE_TASK or TASKING_DECLARE_TEMPLATE_TASK macros. + For example, \c ConcurrentCallTask is an alias to the CustomTask that is defined + to work with \c ConcurrentCall as an associated task class. + The following table contains all the built-in tasks and their associated task classes: + + \table + \header + \li Aliased Task Name (Tasking Namespace) + \li Associated Task Class + \li Brief Description + \row + \li ConcurrentCallTask + \li ConcurrentCall + \li Starts an asynchronous task. Runs in a separate thread. + \row + \li NetworkQueryTask + \li NetworkQuery + \li Sends a network query. + \row + \li TaskTreeTask + \li TaskTree + \li Starts a nested task tree. + \row + \li TimeoutTask + \li \c std::chrono::milliseconds + \li Starts a timer. + \row + \li WaitForBarrierTask + \li MultiBarrier + \li Starts an asynchronous task waiting for the barrier to pass. + \endtable +*/ + +/*! + \typealias CustomTask::Task + + Type alias for \c Adapter::Type. + + This is the associated task's type. +*/ + +/*! + \typealias CustomTask::EndHandler + + Type alias for \c std::function. +*/ + +/*! + \fn template template CustomTask::CustomTask(SetupHandler &&setup, const EndHandler &done, const EndHandler &error) + + Constructs the CustomTask instance and attaches the \a setup, \a done, and \a error + handlers to the task. When the running task tree is about to start the task, + it instantiates the associated \l Task object, invokes \a setup handler with a \e reference + to the created task, and starts it. When the running task finishes with success or an error, + the task tree invokes \a done or \a error handler, respectively, + with a \e {const reference} to the created task. + + The passed \a setup handler is either of the \c std::function or + \c std::function type. For example: + + \code + static void parseAndLog(const QString &input); + + ... + + const QString input = ...; + + const auto onFirstSetup = [input](ConcurrentCall &task) { + if (input == "Skip") + return SetupResult::StopWithDone; // This task won't start, the next one will + if (input == "Error") + return SetupResult::StopWithError; // This task and the next one won't start + task.setConcurrentCallData(parseAndLog, input); + // This task will start, and the next one will start after this one finished with success + return SetupResult::Continue; + }; + + const auto onSecondSetup = [input](ConcurrentCall &task) { + task.setConcurrentCallData(parseAndLog, input); + }; + + const Group group { + ConcurrentCallTask(onFirstSetup), + ConcurrentCallTask(onSecondSetup) + }; + \endcode + + When the passed \a setup handler is of the \c std::function type, + the return value of the handler instructs the running tree on how to proceed after + the handler's invocation is finished. The default return value of SetupResult::Continue + instructs the tree to continue running, i.e. to execute the associated \c Task. + The return value of SetupResult::StopWithDone or SetupResult::StopWithError instructs + the tree to skip the task's execution and finish immediately with success or an error, + respectively. + When the return type is either SetupResult::StopWithDone or SetupResult::StopWithError, + the task's \a done or \a error handler (even if provided) are not called afterwards. + + The \a setup handler may be of a shortened form of std::function, + i.e. the return value is void. In this case it's assumed that the return value is + SetupResult::Continue by default. + + When the running task finishes, one of \a done or \a error handlers is called, + depending on whether it finished with success or an error, respectively. + Both handlers are of std::function type. + + \sa onSetup(), onDone(), onError() +*/ + +/*! + \fn template template CustomTask &CustomTask::onSetup(SetupHandler &&handler) + + Attaches the setup \a handler to \c this task. + The \a handler is invoked when the task is about to be started. + + This function enables defining the task's details with a + \l {https://en.wikipedia.org/wiki/Fluent_interface}{fluent interface} style: + + \code + const auto onQuerySetup = [](NetworkQuery &task) { ... }; + const auto onQueryError = [](const NetworkQuery &task) { ... }; + + const Group group { + NetworkQueryTask(onQuerySetup, {}, onQueryError), + NetworkQueryTask().onSetup(onQuerySetup).onError(onQueryError), // fluent interface style + NetworkQueryTask(onQuerySetup, {}, onQueryError).withTimeout(500ms) + } + \endcode + + \sa CustomTask(), onDone(), onError() +*/ + +/*! + \fn template CustomTask &CustomTask::onDone(const EndHandler &handler) + + Attaches the done \a handler to \c this task. + The handler is invoked when the task finishes with success. + + This function enables defining the task's details with a fluent interface style. + + \sa CustomTask(), onSetup(), onError() +*/ + +/*! + \fn template CustomTask &CustomTask::onError(const EndHandler &handler) + + Attaches the error \a handler to \c this task. + The handler is invoked when the task finishes with an error. + + This function enables defining the task's details with a fluent interface style. + + \sa CustomTask(), onSetup(), onDone() +*/ + +/*! + \fn template GroupItem CustomTask::withTimeout(std::chrono::milliseconds timeout, const GroupItem::GroupEndHandler &handler) const + + Attaches \c TimeoutTask to a copy of \c this task, elapsing after \a timeout in milliseconds, + with an optionally provided timeout \a handler, and returns the coupled item. + + When the task finishes before \a timeout passes, + the returned item finishes immediately with the task's result. + Otherwise, the \a handler is invoked (if provided), the task is stopped, + and the returned item finishes with an error. + + \sa onSetup() +*/ + /*! \macro TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass) \relates Tasking @@ -158,13 +521,6 @@ private: For more information on implementing the custom task adapters, refer to \l {Task Adapters}. */ -/*! - \class Tasking::GroupItem - \inheaderfile solutions/tasking/tasktree.h - \inmodule TaskingSolution - \brief The GroupItem class represents the basic element for composing nested tree structures. -*/ - /*! \enum Tasking::WorkflowPolicy diff --git a/src/libs/solutions/tasking/tasktree.h b/src/libs/solutions/tasking/tasktree.h index 47ad221728a..88f89ef0c51 100644 --- a/src/libs/solutions/tasking/tasktree.h +++ b/src/libs/solutions/tasking/tasktree.h @@ -321,14 +321,14 @@ public: using EndHandler = std::function; static Adapter *createAdapter() { return new Adapter; } CustomTask() : GroupItem({&createAdapter}) {} - template - CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) - : GroupItem({&createAdapter, wrapSetup(std::forward(function)), + template + CustomTask(SetupHandler &&setup, const EndHandler &done = {}, const EndHandler &error = {}) + : GroupItem({&createAdapter, wrapSetup(std::forward(setup)), wrapEnd(done), wrapEnd(error)}) {} - template - CustomTask &onSetup(SetupFunction &&function) { - setTaskSetupHandler(wrapSetup(std::forward(function))); + template + CustomTask &onSetup(SetupHandler &&handler) { + setTaskSetupHandler(wrapSetup(std::forward(handler))); return *this; } CustomTask &onDone(const EndHandler &handler) { @@ -346,20 +346,20 @@ public: } private: - template - static GroupItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { + template + static GroupItem::TaskSetupHandler wrapSetup(SetupHandler &&handler) { static constexpr bool isDynamic = std::is_same_v, typename Adapter::Type &>>; + std::invoke_result_t, typename Adapter::Type &>>; constexpr bool isVoid = std::is_same_v, typename Adapter::Type &>>; + std::invoke_result_t, typename Adapter::Type &>>; static_assert(isDynamic || isVoid, "Task setup handler needs to take (Task &) as an argument and has to return " "void or SetupResult. The passed handler doesn't fulfill these requirements."); return [=](TaskInterface &taskInterface) { Adapter &adapter = static_cast(taskInterface); if constexpr (isDynamic) - return std::invoke(function, *adapter.task()); - std::invoke(function, *adapter.task()); + return std::invoke(handler, *adapter.task()); + std::invoke(handler, *adapter.task()); return SetupResult::Continue; }; };