TaskTree: Document GroupItem and subclasses

Adapt some type and argument names accordingly.

Change-Id: I1b127eacfd0a71574ee9e9a7628ec572620798cb
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Jarek Kobus
2023-06-20 18:54:38 +02:00
parent 29ade7d682
commit 152d543cdf
3 changed files with 376 additions and 20 deletions

View File

@@ -82,7 +82,7 @@ public:
"is not reachable in the running tree. " "is not reachable in the running tree. "
"It is possible that no barrier was added to the tree, " "It is possible that no barrier was added to the tree, "
"or the storage is not reachable from where it is referenced. " "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; return SetupResult::StopWithError;
} }
Barrier *activeSharedBarrier = activeBarrier->barrier(); Barrier *activeSharedBarrier = activeBarrier->barrier();

View File

@@ -138,6 +138,369 @@ private:
Returns the const pointer to the associated \c Task instance. 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<ResultType>
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<ResultType> or \c NetworkQueryTask:
\code
const Group group {
NetworkQueryTask(...),
ConcurrentCallTask<int>(...)
};
\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<QString>(...)
}
};
\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<MyCustomStruct> 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<QImage> &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<QImage>(onSecondSetup)
};
\endcode
*/
/*!
\fn Group::Group(const QList<GroupItem> &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<GroupItem> 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<GroupItem> 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<QString>(...)
}
};
\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<T> is an alias to the CustomTask that is defined
to work with \c ConcurrentCall<T> 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<ReturnType>
\li ConcurrentCall<ReturnType>
\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<Limit>
\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<void(const Task &)>.
*/
/*!
\fn template <typename Adapter> template <typename SetupHandler> CustomTask<Adapter>::CustomTask<Adapter>(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<SetupResult(Task &)> or
\c std::function<void(Task &)> type. For example:
\code
static void parseAndLog(const QString &input);
...
const QString input = ...;
const auto onFirstSetup = [input](ConcurrentCall<void> &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<void> &task) {
task.setConcurrentCallData(parseAndLog, input);
};
const Group group {
ConcurrentCallTask<void>(onFirstSetup),
ConcurrentCallTask<void>(onSecondSetup)
};
\endcode
When the passed \a setup handler is of the \c std::function<SetupResult(Task &)> 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<void(Task &)>,
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<void(const Task &)> type.
\sa onSetup(), onDone(), onError()
*/
/*!
\fn template <typename Adapter> template <typename SetupHandler> CustomTask<Adapter> &CustomTask<Adapter>::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 <typename Adapter> CustomTask<Adapter> &CustomTask<Adapter>::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 <typename Adapter> CustomTask<Adapter> &CustomTask<Adapter>::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 <typename Adapter> GroupItem CustomTask<Adapter>::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) \macro TASKING_DECLARE_TASK(CustomTaskName, TaskAdapterClass)
\relates Tasking \relates Tasking
@@ -158,13 +521,6 @@ private:
For more information on implementing the custom task adapters, refer to \l {Task Adapters}. 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 \enum Tasking::WorkflowPolicy

View File

@@ -321,14 +321,14 @@ public:
using EndHandler = std::function<void(const Task &)>; using EndHandler = std::function<void(const Task &)>;
static Adapter *createAdapter() { return new Adapter; } static Adapter *createAdapter() { return new Adapter; }
CustomTask() : GroupItem({&createAdapter}) {} CustomTask() : GroupItem({&createAdapter}) {}
template <typename SetupFunction> template <typename SetupHandler>
CustomTask(SetupFunction &&function, const EndHandler &done = {}, const EndHandler &error = {}) CustomTask(SetupHandler &&setup, const EndHandler &done = {}, const EndHandler &error = {})
: GroupItem({&createAdapter, wrapSetup(std::forward<SetupFunction>(function)), : GroupItem({&createAdapter, wrapSetup(std::forward<SetupHandler>(setup)),
wrapEnd(done), wrapEnd(error)}) {} wrapEnd(done), wrapEnd(error)}) {}
template <typename SetupFunction> template <typename SetupHandler>
CustomTask &onSetup(SetupFunction &&function) { CustomTask &onSetup(SetupHandler &&handler) {
setTaskSetupHandler(wrapSetup(std::forward<SetupFunction>(function))); setTaskSetupHandler(wrapSetup(std::forward<SetupHandler>(handler)));
return *this; return *this;
} }
CustomTask &onDone(const EndHandler &handler) { CustomTask &onDone(const EndHandler &handler) {
@@ -346,20 +346,20 @@ public:
} }
private: private:
template<typename SetupFunction> template<typename SetupHandler>
static GroupItem::TaskSetupHandler wrapSetup(SetupFunction &&function) { static GroupItem::TaskSetupHandler wrapSetup(SetupHandler &&handler) {
static constexpr bool isDynamic = std::is_same_v<SetupResult, static constexpr bool isDynamic = std::is_same_v<SetupResult,
std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>; std::invoke_result_t<std::decay_t<SetupHandler>, typename Adapter::Type &>>;
constexpr bool isVoid = std::is_same_v<void, constexpr bool isVoid = std::is_same_v<void,
std::invoke_result_t<std::decay_t<SetupFunction>, typename Adapter::Type &>>; std::invoke_result_t<std::decay_t<SetupHandler>, typename Adapter::Type &>>;
static_assert(isDynamic || isVoid, static_assert(isDynamic || isVoid,
"Task setup handler needs to take (Task &) as an argument and has to return " "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."); "void or SetupResult. The passed handler doesn't fulfill these requirements.");
return [=](TaskInterface &taskInterface) { return [=](TaskInterface &taskInterface) {
Adapter &adapter = static_cast<Adapter &>(taskInterface); Adapter &adapter = static_cast<Adapter &>(taskInterface);
if constexpr (isDynamic) if constexpr (isDynamic)
return std::invoke(function, *adapter.task()); return std::invoke(handler, *adapter.task());
std::invoke(function, *adapter.task()); std::invoke(handler, *adapter.task());
return SetupResult::Continue; return SetupResult::Continue;
}; };
}; };