Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
tasktree.cpp
Go to the documentation of this file.
1// Copyright (C) 2024 Jarek Kobus
2// Copyright (C) 2024 The Qt Company Ltd.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "tasktree.h"
6
7#include "barrier.h"
8#include "conditional.h"
9
10#include <QtCore/QDebug>
11#include <QtCore/QEventLoop>
12#include <QtCore/QFutureWatcher>
13#include <QtCore/QHash>
14#include <QtCore/QMetaEnum>
15#include <QtCore/QMutex>
16#include <QtCore/QPointer>
17#include <QtCore/QPromise>
18#include <QtCore/QSet>
19#include <QtCore/QTime>
20#include <QtCore/QTimer>
21
22using namespace Qt::StringLiterals;
23using namespace std::chrono;
24
25QT_BEGIN_NAMESPACE
26
27namespace Tasking {
28
29// That's cut down qtcassert.{c,h} to avoid the dependency.
30#define QT_STRING(cond) qDebug("SOFT ASSERT: \"%s\" in %s: %s", cond, __FILE__, QT_STRINGIFY(__LINE__))
31#define QT_ASSERT(cond, action) if (Q_LIKELY(cond)) {} else { QT_STRING(#cond); action; } do {} while (0)
32#define QT_CHECK(cond) if (cond) {} else { QT_STRING(#cond); } do {} while (0)
33
34class Guard
35{
37public:
38 Guard() = default;
39 ~Guard() { QT_CHECK(m_lockCount == 0); }
40 bool isLocked() const { return m_lockCount; }
41private:
42 int m_lockCount = 0;
43 friend class GuardLocker;
44};
45
55
56/*!
57 \module TaskingSolution
58 \title Tasking Solution
59 \ingroup solutions-modules
60 \brief Contains a general purpose Tasking solution.
61
62 The Tasking solution depends on Qt only, and doesn't depend on any \QC specific code.
63*/
64
65/*!
66 \namespace Tasking
67 \inmodule TaskingSolution
68 \brief The Tasking namespace encloses all classes and global functions of the Tasking solution.
69*/
70
71/*!
72 \class Tasking::TaskInterface
73 \inheaderfile solutions/tasking/tasktree.h
74 \inmodule TaskingSolution
75 \brief TaskInterface is the abstract base class for implementing custom task adapters.
76 \reentrant
77
78 To implement a custom task adapter, derive your adapter from the
79 \c TaskAdapter<Task> class template. TaskAdapter automatically creates and destroys
80 the custom task instance and associates the adapter with a given \c Task type.
81*/
82
83/*!
84 \fn virtual void TaskInterface::start()
85
86 This method is called by the running TaskTree for starting the \c Task instance.
87 Reimplement this method in \c TaskAdapter<Task>'s subclass in order to start the
88 associated task.
89
90 Use TaskAdapter::task() to access the associated \c Task instance.
91
92 \sa done(), TaskAdapter::task()
93*/
94
95/*!
96 \fn void TaskInterface::done(DoneResult result)
97
98 Emit this signal from the \c TaskAdapter<Task>'s subclass, when the \c Task is finished.
99 Pass DoneResult::Success as a \a result argument when the task finishes with success;
100 otherwise, when an error occurs, pass DoneResult::Error.
101*/
102
103/*!
104 \class Tasking::TaskAdapter
105 \inheaderfile solutions/tasking/tasktree.h
106 \inmodule TaskingSolution
107 \brief A class template for implementing custom task adapters.
108 \reentrant
109
110 The TaskAdapter class template is responsible for creating a task of the \c Task type,
111 starting it, and reporting success or an error when the task is finished.
112 It also associates the adapter with a given \c Task type.
113
114 Reimplement this class with the actual \c Task type to adapt the task's interface
115 into the general TaskTree's interface for managing the \c Task instances.
116
117 Each subclass needs to provide a public default constructor,
118 implement the start() method, and emit the done() signal when the task is finished.
119 Use task() to access the associated \c Task instance.
120
121 To use your task adapter inside the task tree, create an alias to the
122 Tasking::CustomTask template passing your task adapter as a template parameter:
123 \code
124 // Defines actual worker
125 class Worker {...};
126
127 // Adapts Worker's interface to work with task tree
128 class WorkerTaskAdapter : public TaskAdapter<Worker> {...};
129
130 // Defines WorkerTask as a new custom task type to be placed inside Group items
131 using WorkerTask = CustomTask<WorkerTaskAdapter>;
132 \endcode
133
134 Optionally, you may pass a custom \c Deleter for the associated \c Task
135 as a second template parameter of your \c TaskAdapter subclass.
136 When the \c Deleter parameter is omitted, the \c std::default_delete<Task> is used by default.
137 The custom \c Deleter is useful when the destructor of the running \c Task
138 may potentially block the caller thread. Instead of blocking, the custom deleter may move
139 the running task into a separate thread and implement the blocking destruction there.
140 In this way, the fast destruction (seen from the caller thread) of the running task
141 with a blocking destructor may be achieved.
142
143 For more information on implementing the custom task adapters, refer to \l {Task Adapters}.
144
145 \sa start(), done(), task()
146*/
147
148/*!
149 \fn template <typename Task, typename Deleter = std::default_delete<Task>> TaskAdapter<Task, Deleter>::TaskAdapter<Task, Deleter>()
150
151 Creates a task adapter for the given \c Task type.
152
153 Internally, it creates an instance of \c Task, which is accessible via the task() method.
154 The optionally provided \c Deleter is used instead of the \c Task destructor.
155 When \c Deleter is omitted, the \c std::default_delete<Task> is used by default.
156
157 \sa task()
158*/
159
160/*!
161 \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task()
162
163 Returns the pointer to the associated \c Task instance.
164*/
165
166/*!
167 \fn template <typename Task, typename Deleter = std::default_delete<Task>> Task *TaskAdapter<Task, Deleter>::task() const
168 \overload
169
170 Returns the \c const pointer to the associated \c Task instance.
171*/
172
173/*!
174 \class Tasking::Storage
175 \inheaderfile solutions/tasking/tasktree.h
176 \inmodule TaskingSolution
177 \brief A class template for custom data exchange in the running task tree.
178 \reentrant
179
180 The Storage class template is responsible for dynamically creating and destructing objects
181 of the custom \c StorageStruct type. The creation and destruction are managed by the
182 running task tree. If a Storage object is placed inside a \l {Tasking::Group} {Group} element,
183 the running task tree creates the \c StorageStruct object when the group is started and before
184 the group's setup handler is called. Later, whenever any handler inside this group is called,
185 the task tree activates the previously created instance of the \c StorageStruct object.
186 This includes all tasks' and groups' setup and done handlers inside the group where the
187 Storage object was placed, also within the nested groups.
188 When a copy of the Storage object is passed to the handler via the lambda capture,
189 the handler may access the instance activated by the running task tree via the
190 \l {Tasking::Storage::operator->()} {operator->()},
191 \l {Tasking::Storage::operator*()} {operator*()}, or activeStorage() method.
192 If two handlers capture the same Storage object, one of them may store a custom data there,
193 and the other may read it afterwards.
194 When the group is finished, the previously created instance of the \c StorageStruct
195 object is destroyed after the group's done handler is called.
196
197 An example of data exchange between tasks:
198
199 \code
200 const Storage<QString> storage;
201
202 const auto onFirstDone = [storage](const Task &task) {
203 // Assings QString, taken from the first task result, to the active QString instance
204 // of the Storage object.
205 *storage = task.getResultAsString();
206 };
207
208 const auto onSecondSetup = [storage](Task &task) {
209 // Reads QString from the active QString instance of the Storage object and use it to
210 // configure the second task before start.
211 task.configureWithString(*storage);
212 };
213
214 const Group root {
215 // The running task tree creates QString instance when root in entered
216 storage,
217 // The done handler of the first task stores the QString in the storage
218 TaskItem(..., onFirstDone),
219 // The setup handler of the second task reads the QString from the storage
220 TaskItem(onSecondSetup, ...)
221 };
222 \endcode
223
224 Since the root group executes its tasks sequentially, the \c onFirstDone handler
225 is always called before the \c onSecondSetup handler. This means that the QString data,
226 read from the \c storage inside the \c onSecondSetup handler's body,
227 has already been set by the \c onFirstDone handler.
228 You can always rely on it in \l {Tasking::sequential} {sequential} execution mode.
229
230 The Storage internals are shared between all of its copies. That is why the copies of the
231 Storage object inside the handlers' lambda captures still refer to the same Storage instance.
232 You may place multiple Storage objects inside one \l {Tasking::Group} {Group} element,
233 provided that they do not include copies of the same Storage object.
234 Otherwise, an assert is triggered at runtime that includes an error message.
235 However, you can place copies of the same Storage object in different
236 \l {Tasking::Group} {Group} elements of the same recipe. In this case, the running task
237 tree will create multiple instances of the \c StorageStruct objects (one for each copy)
238 and storage shadowing will take place. Storage shadowing works in a similar way
239 to C++ variable shadowing inside the nested blocks of code:
240
241 \code
242 Storage<QString> storage;
243
244 const Group root {
245 storage, // Top copy, 1st instance of StorageStruct
246 onGroupSetup([storage] { ... }), // Top copy is active
247 Group {
248 storage, // Nested copy, 2nd instance of StorageStruct,
249 // shadows Top copy
250 onGroupSetup([storage] { ... }), // Nested copy is active
251 },
252 Group {
253 onGroupSetup([storage] { ... }), // Top copy is active
254 }
255 };
256 \endcode
257
258 The Storage objects may also be used for passing the initial data to the executed task tree,
259 and for reading the final data out of the task tree before it finishes.
260 To do this, use \l {TaskTree::onStorageSetup()} {onStorageSetup()} or
261 \l {TaskTree::onStorageDone()} {onStorageDone()}, respectively.
262
263 \note If you use an unreachable Storage object inside the handler,
264 because you forgot to place the storage in the recipe,
265 or placed it, but not in any handler's ancestor group,
266 you may expect a crash, preceded by the following message:
267 \e {The referenced storage is not reachable in the running tree.
268 A nullptr will be returned which might lead to a crash in the calling code.
269 It is possible that no storage was added to the tree,
270 or the storage is not reachable from where it is referenced.}
271*/
272
273/*!
274 \fn template <typename StorageStruct> Storage<StorageStruct>::Storage<StorageStruct>()
275
276 Creates a storage for the given \c StorageStruct type.
277
278 \note All copies of \c this object are considered to be the same Storage instance.
279*/
280
281/*!
282 \fn template <typename StorageStruct> StorageStruct &Storage<StorageStruct>::operator*() const noexcept
283
284 Returns a \e reference to the active \c StorageStruct object, created by the running task tree.
285 Use this function only from inside the handler body of any GroupItem element placed
286 in the recipe, otherwise you may expect a crash.
287 Make sure that Storage is placed in any group ancestor of the handler's group item.
288
289 \note The returned reference is valid as long as the group that created this instance
290 is still running.
291
292 \sa activeStorage(), operator->()
293*/
294
295/*!
296 \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::operator->() const noexcept
297
298 Returns a \e pointer to the active \c StorageStruct object, created by the running task tree.
299 Use this function only from inside the handler body of any GroupItem element placed
300 in the recipe, otherwise you may expect a crash.
301 Make sure that Storage is placed in any group ancestor of the handler's group item.
302
303 \note The returned pointer is valid as long as the group that created this instance
304 is still running.
305
306 \sa activeStorage(), operator*()
307*/
308
309/*!
310 \fn template <typename StorageStruct> StorageStruct *Storage<StorageStruct>::activeStorage() const
311
312 Returns a \e pointer to the active \c StorageStruct object, created by the running task tree.
313 Use this function only from inside the handler body of any GroupItem element placed
314 in the recipe, otherwise you may expect a crash.
315 Make sure that Storage is placed in any group ancestor of the handler's group item.
316
317 \note The returned pointer is valid as long as the group that created this instance
318 is still running.
319
320 \sa operator->(), operator*()
321*/
322
323/*!
324 \typealias Tasking::GroupItems
325
326 Type alias for QList<GroupItem>.
327*/
328
329/*!
330 \class Tasking::GroupItem
331 \inheaderfile solutions/tasking/tasktree.h
332 \inmodule TaskingSolution
333 \brief GroupItem represents the basic element that may be a part of any Group.
334 \reentrant
335
336 GroupItem is a basic element that may be a part of any \l {Tasking::Group} {Group}.
337 It encapsulates the functionality provided by any GroupItem's subclass.
338 It is a value type and it is safe to copy the GroupItem instance,
339 even when it is originally created via the subclass' constructor.
340
341 There are four main kinds of GroupItem:
342 \table
343 \header
344 \li GroupItem Kind
345 \li Brief Description
346 \row
347 \li \l CustomTask
348 \li Defines asynchronous task type and task's start, done, and error handlers.
349 Aliased with a unique task name, such as, \c ConcurrentCallTask<ResultType>
350 or \c NetworkQueryTask. Asynchronous tasks are the main reason for using a task tree.
351 \row
352 \li \l {Tasking::Group} {Group}
353 \li A container for other group items. Since the group is of the GroupItem type,
354 it's possible to nest it inside another group. The group is seen by its parent
355 as a single asynchronous task.
356 \row
357 \li GroupItem containing \l {Tasking::Storage} {Storage}
358 \li Enables the child tasks of a group to exchange data. When GroupItem containing
359 \l {Tasking::Storage} {Storage} is placed inside a group, the task tree instantiates
360 the storage's data object just before the group is entered,
361 and destroys it just after the group is left.
362 \row
363 \li Other group control items
364 \li The items returned by \l {Tasking::parallelLimit()} {parallelLimit()} or
365 \l {Tasking::workflowPolicy()} {workflowPolicy()} influence the group's behavior.
366 The items returned by \l {Tasking::onGroupSetup()} {onGroupSetup()} or
367 \l {Tasking::onGroupDone()} {onGroupDone()} define custom handlers called when
368 the group starts or ends execution.
369 \endtable
370*/
371
372/*!
373 \fn template <typename StorageStruct> GroupItem::GroupItem(const Storage<StorageStruct> &storage)
374
375 Constructs a \c GroupItem element holding the \a storage object.
376
377 When the \l {Tasking::Group} {Group} element containing \e this GroupItem is entered
378 by the running task tree, an instance of the \c StorageStruct is created dynamically.
379
380 When that group is about to be left after its execution, the previously instantiated
381 \c StorageStruct is deleted.
382
383 The dynamically created instance of \c StorageStruct is accessible from inside any
384 handler body of the parent \l {Tasking::Group} {Group} element,
385 including nested groups and its tasks, via the
386 \l {Tasking::Storage::operator->()} {Storage::operator->()},
387 \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method.
388
389 \sa {Tasking::Storage} {Storage}
390*/
391
392/*!
393 \fn GroupItem::GroupItem(const GroupItems &items)
394
395 Constructs a \c GroupItem element with a given list of \a items.
396
397 When this \c GroupItem element is parsed by the TaskTree, it is simply replaced with
398 its \a items.
399
400 This constructor is useful when constructing a \l {Tasking::Group} {Group} element with
401 lists of \c GroupItem elements:
402
403 \code
404 static QList<GroupItems> getItems();
405
406 ...
407
408 const Group root {
409 parallel,
410 finishAllAndSuccess,
411 getItems(), // OK, getItems() list is wrapped into a single GroupItem element
412 onGroupSetup(...),
413 onGroupDone(...)
414 };
415 \endcode
416
417 If you want to create a subtree, use \l {Tasking::Group} {Group} instead.
418
419 \note Don't confuse this \c GroupItem with the \l {Tasking::Group} {Group} element, as
420 \l {Tasking::Group} {Group} keeps its children nested
421 after being parsed by the task tree, while this \c GroupItem does not.
422
423 \sa {Tasking::Group} {Group}
424*/
425
426/*!
427 \fn Tasking::GroupItem(std::initializer_list<GroupItem> items)
428 \overload
429 \sa GroupItem(const GroupItems &items)
430*/
431
432/*!
433 \class Tasking::Group
434 \inheaderfile solutions/tasking/tasktree.h
435 \inmodule TaskingSolution
436 \brief Group represents the basic element for composing declarative recipes describing
437 how to execute and handle a nested tree of asynchronous tasks.
438 \reentrant
439
440 Group is a container for other group items. It encloses child tasks into one unit,
441 which is seen by the group's parent as a single, asynchronous task.
442 Since Group is of the GroupItem type, it may also be a child of Group.
443
444 Insert child tasks into the group by using aliased custom task names, such as,
445 \c ConcurrentCallTask<ResultType> or \c NetworkQueryTask:
446
447 \code
448 const Group group {
449 NetworkQueryTask(...),
450 ConcurrentCallTask<int>(...)
451 };
452 \endcode
453
454 The group's behavior may be customized by inserting the items returned by
455 \l {Tasking::parallelLimit()} {parallelLimit()} or
456 \l {Tasking::workflowPolicy()} {workflowPolicy()} functions:
457
458 \code
459 const Group group {
460 parallel,
461 continueOnError,
462 NetworkQueryTask(...),
463 NetworkQueryTask(...)
464 };
465 \endcode
466
467 The group may contain nested groups:
468
469 \code
470 const Group group {
471 finishAllAndSuccess,
472 NetworkQueryTask(...),
473 Group {
474 NetworkQueryTask(...),
475 Group {
476 parallel,
477 NetworkQueryTask(...),
478 NetworkQueryTask(...),
479 }
480 ConcurrentCallTask<QString>(...)
481 }
482 };
483 \endcode
484
485 The group may dynamically instantiate a custom storage structure, which may be used for
486 inter-task data exchange:
487
488 \code
489 struct MyCustomStruct { QByteArray data; };
490
491 Storage<MyCustomStruct> storage;
492
493 const auto onFirstSetup = [](NetworkQuery &task) { ... };
494 const auto onFirstDone = [storage](const NetworkQuery &task) {
495 // storage-> gives a pointer to MyCustomStruct instance,
496 // created dynamically by the running task tree.
497 storage->data = task.reply()->readAll();
498 };
499 const auto onSecondSetup = [storage](ConcurrentCall<QImage> &task) {
500 // storage-> gives a pointer to MyCustomStruct. Since the group is sequential,
501 // the stored MyCustomStruct was already updated inside the onFirstDone handler.
502 const QByteArray storedData = storage->data;
503 };
504
505 const Group group {
506 // When the group is entered by a running task tree, it creates MyCustomStruct
507 // instance dynamically. It is later accessible from all handlers via
508 // the *storage or storage-> operators.
509 sequential,
510 storage,
511 NetworkQueryTask(onFirstSetup, onFirstDone, CallDoneIf::Success),
512 ConcurrentCallTask<QImage>(onSecondSetup)
513 };
514 \endcode
515*/
516
517/*!
518 \fn Group::Group(const GroupItems &children)
519
520 Constructs a group with a given list of \a children.
521
522 This constructor is useful when the child items of the group are not known at compile time,
523 but later, at runtime:
524
525 \code
526 const QStringList sourceList = ...;
527
528 GroupItems groupItems { parallel };
529
530 for (const QString &source : sourceList) {
531 const NetworkQueryTask task(...); // use source for setup handler
532 groupItems << task;
533 }
534
535 const Group group(groupItems);
536 \endcode
537*/
538
539/*!
540 \fn Group::Group(std::initializer_list<GroupItem> children)
541
542 Constructs a group from \c std::initializer_list given by \a children.
543
544 This constructor is useful when all child items of the group are known at compile time:
545
546 \code
547 const Group group {
548 finishAllAndSuccess,
549 NetworkQueryTask(...),
550 Group {
551 NetworkQueryTask(...),
552 Group {
553 parallel,
554 NetworkQueryTask(...),
555 NetworkQueryTask(...),
556 }
557 ConcurrentCallTask<QString>(...)
558 }
559 };
560 \endcode
561*/
562
563/*!
564 \class Tasking::Sync
565 \inheaderfile solutions/tasking/tasktree.h
566 \inmodule TaskingSolution
567 \brief Synchronously executes a custom handler between other tasks.
568 \reentrant
569
570 \c Sync is useful when you want to execute an additional handler between other tasks.
571 \c Sync is seen by its parent \l {Tasking::Group} {Group} as any other task.
572 Avoid long-running execution of the \c Sync's handler body, since it is executed
573 synchronously from the caller thread. If that is unavoidable, consider using
574 \c ConcurrentCallTask instead.
575*/
576
577/*!
578 \fn template <typename Handler> Sync::Sync(Handler &&handler)
579
580 Constructs an element that executes a passed \a handler synchronously.
581 The \c Handler is of the \c std::function<DoneResult()> type.
582 The DoneResult value, returned by the \a handler, is considered during parent group's
583 \l {workflowPolicy} {workflow policy} resolution.
584 Optionally, the shortened form of \c std::function<void()> is also accepted.
585 In this case, it's assumed that the return value is DoneResult::Success.
586
587 The passed \a handler executes synchronously from the caller thread, so avoid a long-running
588 execution of the handler body. Otherwise, consider using \c ConcurrentCallTask.
589
590 \note The \c Sync element is not counted as a task when reporting task tree progress,
591 and is not included in TaskTree::taskCount() or TaskTree::progressMaximum().
592*/
593
594/*!
595 \class Tasking::CustomTask
596 \inheaderfile solutions/tasking/tasktree.h
597 \inmodule TaskingSolution
598 \brief A class template used for declaring custom task items and defining their setup
599 and done handlers.
600 \reentrant
601
602 Describes custom task items within task tree recipes.
603
604 Custom task names are aliased with unique names using the \c CustomTask template
605 with a given TaskAdapter subclass as a template parameter.
606 For example, \c ConcurrentCallTask<T> is an alias to the \c CustomTask that is defined
607 to work with \c ConcurrentCall<T> as an associated task class.
608 The following table contains example custom tasks and their associated task classes:
609
610 \table
611 \header
612 \li Aliased Task Name (Tasking Namespace)
613 \li Associated Task Class
614 \li Brief Description
615 \row
616 \li ConcurrentCallTask<ReturnType>
617 \li ConcurrentCall<ReturnType>
618 \li Starts an asynchronous task. Runs in a separate thread.
619 \row
620 \li NetworkQueryTask
621 \li NetworkQuery
622 \li Sends a network query.
623 \row
624 \li TaskTreeTask
625 \li TaskTree
626 \li Starts a nested task tree.
627 \row
628 \li TimeoutTask
629 \li \c std::chrono::milliseconds
630 \li Starts a timer.
631 \row
632 \li WaitForBarrierTask
633 \li MultiBarrier<Limit>
634 \li Starts an asynchronous task waiting for the barrier to pass.
635 \endtable
636*/
637
638/*!
639 \typealias Tasking::CustomTask::Task
640
641 Type alias for the task type associated with the custom task's \c Adapter.
642*/
643
644/*!
645 \typealias Tasking::CustomTask::Deleter
646
647 Type alias for the task's type deleter associated with the custom task's \c Adapter.
648*/
649
650/*!
651 \typealias Tasking::CustomTask::TaskSetupHandler
652
653 Type alias for \c std::function<SetupResult(Task &)>.
654
655 The \c TaskSetupHandler is an optional argument of a custom task element's constructor.
656 Any function with the above signature, when passed as a task setup handler,
657 will be called by the running task tree after the task is created and before it is started.
658
659 Inside the body of the handler, you may configure the task according to your needs.
660 The additional parameters, including storages, may be passed to the handler
661 via the lambda capture.
662 You can decide dynamically whether the task should be started or skipped with
663 success or an error.
664
665 \note Do not start the task inside the start handler by yourself. Leave it for TaskTree,
666 otherwise the behavior is undefined.
667
668 The return value of the handler instructs the running task tree on how to proceed
669 after the handler's invocation is finished. The return value of SetupResult::Continue
670 instructs the task tree to continue running, that is, to execute the associated \c Task.
671 The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError
672 instructs the task tree to skip the task's execution and finish it immediately with
673 success or an error, respectively.
674
675 When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError,
676 the task's done handler (if provided) isn't called afterwards.
677
678 The constructor of a custom task accepts also functions in the shortened form of
679 \c std::function<void(Task &)>, that is, the return value is \c void.
680 In this case, it's assumed that the return value is SetupResult::Continue.
681
682 \sa CustomTask(), TaskDoneHandler, GroupSetupHandler
683*/
684
685/*!
686 \typealias Tasking::CustomTask::TaskDoneHandler
687
688 Type alias for \c std::function<DoneResult(const Task &, DoneWith)> or DoneResult.
689
690 The \c TaskDoneHandler is an optional argument of a custom task element's constructor.
691 Any function with the above signature, when passed as a task done handler,
692 will be called by the running task tree after the task execution finished and before
693 the final result of the execution is reported back to the parent group.
694
695 Inside the body of the handler you may retrieve the final data from the finished task.
696 The additional parameters, including storages, may be passed to the handler
697 via the lambda capture.
698 It is also possible to decide dynamically whether the task should finish with its return
699 value, or the final result should be tweaked.
700
701 The DoneWith argument is optional and your done handler may omit it.
702 When provided, it holds the info about the final result of a task that will be
703 reported to its parent.
704
705 If you do not plan to read any data from the finished task,
706 you may omit the \c {const Task &} argument.
707
708 The returned DoneResult value is optional and your handler may return \c void instead.
709 In this case, the final result of the task will be equal to the value indicated by
710 the DoneWith argument. When the handler returns the DoneResult value,
711 the task's final result may be tweaked inside the done handler's body by the returned value.
712
713 For a \c TaskDoneHandler of the DoneResult type, no additional handling is executed,
714 and the task finishes unconditionally with the passed value of DoneResult.
715
716 \sa CustomTask(), TaskSetupHandler, GroupDoneHandler
717*/
718
719/*!
720 \fn template <typename Adapter> template <typename SetupHandler = TaskSetupHandler, typename DoneHandler = TaskDoneHandler> CustomTask<Adapter>::CustomTask(SetupHandler &&setup = TaskSetupHandler(), DoneHandler &&done = TaskDoneHandler(), CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
721
722 Constructs a \c CustomTask instance and attaches the \a setup and \a done handlers to the task.
723 When the running task tree is about to start the task,
724 it instantiates the associated \l Task object, invokes \a setup handler with a \e reference
725 to the created task, and starts it. When the running task finishes,
726 the task tree invokes a \a done handler, with a \c const \e reference to the created task.
727
728 The passed \a setup handler is of the \l TaskSetupHandler type. For example:
729
730 \code
731 static void parseAndLog(const QString &input);
732
733 ...
734
735 const QString input = ...;
736
737 const auto onFirstSetup = [input](ConcurrentCall<void> &task) {
738 if (input == "Skip")
739 return SetupResult::StopWithSuccess; // This task won't start, the next one will
740 if (input == "Error")
741 return SetupResult::StopWithError; // This task and the next one won't start
742 task.setConcurrentCallData(parseAndLog, input);
743 // This task will start, and the next one will start after this one finished with success
744 return SetupResult::Continue;
745 };
746
747 const auto onSecondSetup = [input](ConcurrentCall<void> &task) {
748 task.setConcurrentCallData(parseAndLog, input);
749 };
750
751 const Group group {
752 ConcurrentCallTask<void>(onFirstSetup),
753 ConcurrentCallTask<void>(onSecondSetup)
754 };
755 \endcode
756
757 The \a done handler is of the \l TaskDoneHandler type.
758 By default, the \a done handler is invoked whenever the task finishes.
759 Pass a non-default value for the \a callDoneIf argument when you want the handler to be called
760 only on a successful or failed execution.
761
762 \sa TaskSetupHandler, TaskDoneHandler
763*/
764
765/*!
766 \enum Tasking::WorkflowPolicy
767
768 This enum describes the possible behavior of the Group element when any group's child task
769 finishes its execution. It's also used when the running Group is canceled.
770
771 \value StopOnError
772 Default. Corresponds to the stopOnError global element.
773 If any child task finishes with an error, the group stops and finishes with an error.
774 If all child tasks finished with success, the group finishes with success.
775 If a group is empty, it finishes with success.
776 \value ContinueOnError
777 Corresponds to the continueOnError global element.
778 Similar to stopOnError, but in case any child finishes with an error,
779 the execution continues until all tasks finish, and the group reports an error
780 afterwards, even when some other tasks in the group finished with success.
781 If all child tasks finish successfully, the group finishes with success.
782 If a group is empty, it finishes with success.
783 \value StopOnSuccess
784 Corresponds to the stopOnSuccess global element.
785 If any child task finishes with success, the group stops and finishes with success.
786 If all child tasks finished with an error, the group finishes with an error.
787 If a group is empty, it finishes with an error.
788 \value ContinueOnSuccess
789 Corresponds to the continueOnSuccess global element.
790 Similar to stopOnSuccess, but in case any child finishes successfully,
791 the execution continues until all tasks finish, and the group reports success
792 afterwards, even when some other tasks in the group finished with an error.
793 If all child tasks finish with an error, the group finishes with an error.
794 If a group is empty, it finishes with an error.
795 \value StopOnSuccessOrError
796 Corresponds to the stopOnSuccessOrError global element.
797 The group starts as many tasks as it can. When any task finishes,
798 the group stops and reports the task's result.
799 Useful only in parallel mode.
800 In sequential mode, only the first task is started, and when finished,
801 the group finishes too, so the other tasks are always skipped.
802 If a group is empty, it finishes with an error.
803 \value FinishAllAndSuccess
804 Corresponds to the finishAllAndSuccess global element.
805 The group executes all tasks and ignores their return results. When all
806 tasks finished, the group finishes with success.
807 If a group is empty, it finishes with success.
808 \value FinishAllAndError
809 Corresponds to the finishAllAndError global element.
810 The group executes all tasks and ignores their return results. When all
811 tasks finished, the group finishes with an error.
812 If a group is empty, it finishes with an error.
813
814 Whenever a child task's result causes the Group to stop, that is,
815 in case of StopOnError, StopOnSuccess, or StopOnSuccessOrError policies,
816 the Group cancels the other running child tasks (if any - for example in parallel mode),
817 and skips executing tasks it has not started yet (for example, in the sequential mode -
818 those, that are placed after the failed task). Both canceling and skipping child tasks
819 may happen when parallelLimit() is used.
820
821 The table below summarizes the differences between various workflow policies:
822
823 \table
824 \header
825 \li \l WorkflowPolicy
826 \li Executes all child tasks
827 \li Result
828 \li Result when the group is empty
829 \row
830 \li StopOnError
831 \li Stops when any child task finished with an error and reports an error
832 \li An error when at least one child task failed, success otherwise
833 \li Success
834 \row
835 \li ContinueOnError
836 \li Yes
837 \li An error when at least one child task failed, success otherwise
838 \li Success
839 \row
840 \li StopOnSuccess
841 \li Stops when any child task finished with success and reports success
842 \li Success when at least one child task succeeded, an error otherwise
843 \li An error
844 \row
845 \li ContinueOnSuccess
846 \li Yes
847 \li Success when at least one child task succeeded, an error otherwise
848 \li An error
849 \row
850 \li StopOnSuccessOrError
851 \li Stops when any child task finished and reports child task's result
852 \li Success or an error, depending on the finished child task's result
853 \li An error
854 \row
855 \li FinishAllAndSuccess
856 \li Yes
857 \li Success
858 \li Success
859 \row
860 \li FinishAllAndError
861 \li Yes
862 \li An error
863 \li An error
864 \endtable
865
866 If a child of a group is also a group, the child group runs its tasks according to its own
867 workflow policy. When a parent group stops the running child group because
868 of parent group's workflow policy, that is, when the StopOnError, StopOnSuccess,
869 or StopOnSuccessOrError policy was used for the parent,
870 the child group's result is reported according to the
871 \b Result column and to the \b {child group's workflow policy} row in the table above.
872*/
873
874/*!
875 \variable Tasking::nullItem
876
877 A convenient global group's element indicating a no-op item.
878
879 This is useful in conditional expressions to indicate the absence of an optional element:
880
881 \code
882 const ExecutableItem task = ...;
883 const std::optional<ExecutableItem> optionalTask = ...;
884
885 Group group {
886 task,
887 optionalTask ? *optionalTask : nullItem
888 };
889 \endcode
890*/
891
892/*!
893 \variable Tasking::successItem
894
895 A convenient global executable element containing an empty, successful, synchronous task.
896
897 This is useful in if-statements to indicate that a branch ends with success:
898
899 \code
900 const ExecutableItem conditionalTask = ...;
901
902 Group group {
903 stopOnDone,
904 If (conditionalTask) >> Then {
905 ...
906 } >> Else {
907 successItem
908 },
909 nextTask
910 };
911 \endcode
912
913 In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
914 is chosen, which finishes immediately with success. This causes the \c nextTask to be skipped
915 (because of the stopOnDone workflow policy of the \c group)
916 and the \c group finishes with success.
917
918 \sa errorItem
919*/
920
921/*!
922 \variable Tasking::errorItem
923
924 A convenient global executable element containing an empty, erroneous, synchronous task.
925
926 This is useful in if-statements to indicate that a branch ends with an error:
927
928 \code
929 const ExecutableItem conditionalTask = ...;
930
931 Group group {
932 stopOnError,
933 If (conditionalTask) >> Then {
934 ...
935 } >> Else {
936 errorItem
937 },
938 nextTask
939 };
940 \endcode
941
942 In the above example, if the \c conditionalTask finishes with an error, the \c Else branch
943 is chosen, which finishes immediately with an error. This causes the \c nextTask to be skipped
944 (because of the stopOnError workflow policy of the \c group)
945 and the \c group finishes with an error.
946
947 \sa successItem
948*/
949
950/*!
951 \variable Tasking::sequential
952 A convenient global group's element describing the sequential execution mode.
953
954 This is the default execution mode of the Group element.
955
956 When a Group has no execution mode, it runs in the sequential mode.
957 All the direct child tasks of a group are started in a chain, so that when one task finishes,
958 the next one starts. This enables you to pass the results from the previous task
959 as input to the next task before it starts. This mode guarantees that the next task
960 is started only after the previous task finishes.
961
962 \sa parallel, parallelLimit()
963*/
964
965/*!
966 \variable Tasking::parallel
967 A convenient global group's element describing the parallel execution mode.
968
969 All the direct child tasks of a group are started after the group is started,
970 without waiting for the previous child tasks to finish.
971 In this mode, all child tasks run simultaneously.
972
973 \sa sequential, parallelLimit()
974*/
975
976/*!
977 \variable Tasking::parallelIdealThreadCountLimit
978 A convenient global group's element describing the parallel execution mode with a limited
979 number of tasks running simultanously. The limit is equal to the ideal number of threads
980 excluding the calling thread.
981
982 This is a shortcut to:
983 \code
984 parallelLimit(qMax(QThread::idealThreadCount() - 1, 1))
985 \endcode
986
987 \sa parallel, parallelLimit()
988*/
989
990/*!
991 \variable Tasking::stopOnError
992 A convenient global group's element describing the StopOnError workflow policy.
993
994 This is the default workflow policy of the Group element.
995*/
996
997/*!
998 \variable Tasking::continueOnError
999 A convenient global group's element describing the ContinueOnError workflow policy.
1000*/
1001
1002/*!
1003 \variable Tasking::stopOnSuccess
1004 A convenient global group's element describing the StopOnSuccess workflow policy.
1005*/
1006
1007/*!
1008 \variable Tasking::continueOnSuccess
1009 A convenient global group's element describing the ContinueOnSuccess workflow policy.
1010*/
1011
1012/*!
1013 \variable Tasking::stopOnSuccessOrError
1014 A convenient global group's element describing the StopOnSuccessOrError workflow policy.
1015*/
1016
1017/*!
1018 \variable Tasking::finishAllAndSuccess
1019 A convenient global group's element describing the FinishAllAndSuccess workflow policy.
1020*/
1021
1022/*!
1023 \variable Tasking::finishAllAndError
1024 A convenient global group's element describing the FinishAllAndError workflow policy.
1025*/
1026
1027/*!
1028 \enum Tasking::SetupResult
1029
1030 This enum is optionally returned from the group's or task's setup handler function.
1031 It instructs the running task tree on how to proceed after the setup handler's execution
1032 finished.
1033 \value Continue
1034 Default. The group's or task's execution continues normally.
1035 When a group's or task's setup handler returns void, it's assumed that
1036 it returned Continue.
1037 \value StopWithSuccess
1038 The group's or task's execution stops immediately with success.
1039 When returned from the group's setup handler, all child tasks are skipped,
1040 and the group's onGroupDone() handler is invoked with DoneWith::Success.
1041 The group reports success to its parent. The group's workflow policy is ignored.
1042 When returned from the task's setup handler, the task isn't started,
1043 its done handler isn't invoked, and the task reports success to its parent.
1044 \value StopWithError
1045 The group's or task's execution stops immediately with an error.
1046 When returned from the group's setup handler, all child tasks are skipped,
1047 and the group's onGroupDone() handler is invoked with DoneWith::Error.
1048 The group reports an error to its parent. The group's workflow policy is ignored.
1049 When returned from the task's setup handler, the task isn't started,
1050 its error handler isn't invoked, and the task reports an error to its parent.
1051*/
1052
1053/*!
1054 \enum Tasking::DoneResult
1055
1056 This enum is optionally returned from the group's or task's done handler function.
1057 When the done handler doesn't return any value, that is, its return type is \c void,
1058 its final return value is automatically deduced by the running task tree and reported
1059 to its parent group.
1060
1061 When the done handler returns the DoneResult, you can tweak the final return value
1062 inside the handler.
1063
1064 When the DoneResult is returned by the group's done handler, the group's workflow policy
1065 is ignored.
1066
1067 This enum is also used inside the TaskInterface::done() signal and it indicates whether
1068 the task finished with success or an error.
1069
1070 \value Success
1071 The group's or task's execution ends with success.
1072 \value Error
1073 The group's or task's execution ends with an error.
1074*/
1075
1076/*!
1077 \enum Tasking::DoneWith
1078
1079 This enum is an optional argument for the group's or task's done handler.
1080 It indicates whether the group or task finished with success or an error, or it was canceled.
1081
1082 It is also used as an argument inside the TaskTree::done() signal,
1083 indicating the final result of the TaskTree execution.
1084
1085 \value Success
1086 The group's or task's execution ended with success.
1087 \value Error
1088 The group's or task's execution ended with an error.
1089 \value Cancel
1090 The group's or task's execution was canceled. This happens when the user calls
1091 TaskTree::cancel() for the running task tree or when the group's workflow policy
1092 results in canceling some of its running children.
1093 Tweaking the done handler's final result by returning Tasking::DoneResult from
1094 the handler is no-op when the group's or task's execution was canceled.
1095*/
1096
1097/*!
1098 \enum Tasking::CallDoneIf
1099
1100 This enum is an optional argument for the \l onGroupDone() element or custom task's constructor.
1101 It instructs the task tree on when the group's or task's done handler should be invoked.
1102
1103 \value SuccessOrError
1104 The done handler is always invoked.
1105 \value Success
1106 The done handler is invoked only after successful execution,
1107 that is, when DoneWith::Success.
1108 \value Error
1109 The done handler is invoked only after failed execution,
1110 that is, when DoneWith::Error or when DoneWith::Cancel.
1111*/
1112
1113/*!
1114 \typealias Tasking::GroupItem::GroupSetupHandler
1115
1116 Type alias for \c std::function<SetupResult()>.
1117
1118 The \c GroupSetupHandler is an argument of the onGroupSetup() element.
1119 Any function with the above signature, when passed as a group setup handler,
1120 will be called by the running task tree when the group execution starts.
1121
1122 The return value of the handler instructs the running group on how to proceed
1123 after the handler's invocation is finished. The default return value of SetupResult::Continue
1124 instructs the group to continue running, that is, to start executing its child tasks.
1125 The return value of SetupResult::StopWithSuccess or SetupResult::StopWithError
1126 instructs the group to skip the child tasks' execution and finish immediately with
1127 success or an error, respectively.
1128
1129 When the return type is either SetupResult::StopWithSuccess or SetupResult::StopWithError,
1130 the group's done handler (if provided) is called synchronously immediately afterwards.
1131
1132 \note Even if the group setup handler returns StopWithSuccess or StopWithError,
1133 the group's done handler is invoked. This behavior differs from that of task done handler
1134 and might change in the future.
1135
1136 The onGroupSetup() element accepts also functions in the shortened form of
1137 \c std::function<void()>, that is, the return value is \c void.
1138 In this case, it's assumed that the return value is SetupResult::Continue.
1139
1140 \sa onGroupSetup(), GroupDoneHandler, CustomTask::TaskSetupHandler
1141*/
1142
1143/*!
1144 \typealias Tasking::GroupItem::GroupDoneHandler
1145
1146 Type alias for \c std::function<DoneResult(DoneWith)> or DoneResult.
1147
1148 The \c GroupDoneHandler is an argument of the onGroupDone() element.
1149 Any function with the above signature, when passed as a group done handler,
1150 will be called by the running task tree when the group execution ends.
1151
1152 The DoneWith argument is optional and your done handler may omit it.
1153 When provided, it holds the info about the final result of a group that will be
1154 reported to its parent.
1155
1156 The returned DoneResult value is optional and your handler may return \c void instead.
1157 In this case, the final result of the group will be equal to the value indicated by
1158 the DoneWith argument. When the handler returns the DoneResult value,
1159 the group's final result may be tweaked inside the done handler's body by the returned value.
1160
1161 For a \c GroupDoneHandler of the DoneResult type, no additional handling is executed,
1162 and the group finishes unconditionally with the passed value of DoneResult,
1163 ignoring the group's workflow policy.
1164
1165 \sa onGroupDone(), GroupSetupHandler, CustomTask::TaskDoneHandler
1166*/
1167
1168/*!
1169 \fn template <typename Handler> GroupItem onGroupSetup(Handler &&handler)
1170
1171 Constructs a group's element holding the group setup handler.
1172 The \a handler is invoked whenever the group starts.
1173
1174 The passed \a handler is either of the \c std::function<SetupResult()> or the
1175 \c std::function<void()> type. For more information on a possible handler type, refer to
1176 \l {GroupItem::GroupSetupHandler}.
1177
1178 When the \a handler is invoked, none of the group's child tasks are running yet.
1179
1180 If a group contains the Storage elements, the \a handler is invoked
1181 after the storages are constructed, so that the \a handler may already
1182 perform some initial modifications to the active storages.
1183
1184 \sa GroupItem::GroupSetupHandler, onGroupDone()
1185*/
1186
1187/*!
1188 \fn template <typename Handler> GroupItem onGroupDone(Handler &&handler, CallDoneIf callDoneIf = CallDoneIf::SuccessOrError)
1189
1190 Constructs a group's element holding the group done handler.
1191 By default, the \a handler is invoked whenever the group finishes.
1192 Pass a non-default value for the \a callDoneIf argument when you want the handler to be called
1193 only on a successful or failed execution.
1194 Depending on the group's workflow policy, this handler may also be called
1195 when the running group is canceled (e.g. when stopOnError element was used).
1196
1197 The passed \a handler is of the \c std::function<DoneResult(DoneWith)> type.
1198 Optionally, each of the return DoneResult type or the argument DoneWith type may be omitted
1199 (that is, its return type may be \c void). For more information on a possible handler type,
1200 refer to \l {GroupItem::GroupDoneHandler}.
1201
1202 When the \a handler is invoked, all of the group's child tasks are already finished.
1203
1204 If a group contains the Storage elements, the \a handler is invoked
1205 before the storages are destructed, so that the \a handler may still
1206 perform a last read of the active storages' data.
1207
1208 \sa GroupItem::GroupDoneHandler, onGroupSetup()
1209*/
1210
1211/*!
1212 Constructs a group's element describing the \l{Execution Mode}{execution mode}.
1213
1214 The execution mode element in a Group specifies how the direct child tasks of
1215 the Group are started.
1216
1217 For convenience, when appropriate, the \l sequential or \l parallel global elements
1218 may be used instead.
1219
1220 The \a limit defines the maximum number of direct child tasks running in parallel:
1221
1222 \list
1223 \li When \a limit equals to 0, there is no limit, and all direct child tasks are started
1224 together, in the oder in which they appear in a group. This means the fully parallel
1225 execution, and the \l parallel element may be used instead.
1226
1227 \li When \a limit equals to 1, it means that only one child task may run at the time.
1228 This means the sequential execution, and the \l sequential element may be used instead.
1229 In this case, child tasks run in chain, so the next child task starts after
1230 the previous child task has finished.
1231
1232 \li When other positive number is passed as \a limit, the group's child tasks run
1233 in parallel, but with a limited number of tasks running simultanously.
1234 The \e limit defines the maximum number of tasks running in parallel in a group.
1235 When the group is started, the first batch of tasks is started
1236 (the number of tasks in a batch equals to the passed \a limit, at most),
1237 while the others are kept waiting. When any running task finishes,
1238 the group starts the next remaining one, so that the \e limit of simultaneously
1239 running tasks inside a group isn't exceeded. This repeats on every child task's
1240 finish until all child tasks are started. This enables you to limit the maximum
1241 number of tasks that run simultaneously, for example if running too many processes might
1242 block the machine for a long time.
1243 \endlist
1244
1245 In all execution modes, a group starts tasks in the oder in which they appear.
1246
1247 If a child of a group is also a group, the child group runs its tasks according
1248 to its own execution mode.
1249
1250 \sa sequential, parallel
1251*/
1252
1254{
1255 struct ParallelLimit : GroupItem {
1256 ParallelLimit(int limit) : GroupItem({{}, limit}) {}
1257 };
1258 return ParallelLimit(limit);
1259}
1260
1261/*!
1262 Constructs a group's \l {Workflow Policy} {workflow policy} element for a given \a policy.
1263
1264 For convenience, global elements may be used instead.
1265
1266 \sa stopOnError, continueOnError, stopOnSuccess, continueOnSuccess, stopOnSuccessOrError,
1267 finishAllAndSuccess, finishAllAndError, WorkflowPolicy
1268*/
1270{
1271 struct WorkflowPolicyItem : GroupItem {
1272 WorkflowPolicyItem(WorkflowPolicy policy) : GroupItem({{}, {}, policy}) {}
1273 };
1274 return WorkflowPolicyItem(policy);
1275}
1276
1280
1288
1289// Keep below the above in order to avoid static initialization fiasco.
1290const GroupItem nullItem = Group {};
1293
1294Group operator>>(const For &forItem, const Do &doItem)
1295{
1296 return {forItem.m_loop, doItem.m_children};
1297}
1298
1299Group operator>>(const When &whenItem, const Do &doItem)
1300{
1301 const SingleBarrier barrier;
1302
1303 return {
1304 barrier,
1305 parallel,
1306 whenItem.m_barrierKicker(barrier),
1307 Group {
1308 waitForBarrierTask(barrier),
1309 doItem.m_children
1310 }
1311 };
1312}
1313
1314// Please note the thread_local keyword below guarantees a separate instance per thread.
1315// The s_activeTaskTrees is currently used internally only and is not exposed in the public API.
1316// It serves for withLog() implementation now. Add a note here when a new usage is introduced.
1317static thread_local QList<TaskTree *> s_activeTaskTrees = {};
1318
1320{
1321 QT_ASSERT(s_activeTaskTrees.size(), return nullptr);
1322 return s_activeTaskTrees.back();
1323}
1324
1326{
1327 return success ? DoneResult::Success : DoneResult::Error;
1328}
1329
1330static SetupResult toSetupResult(bool success)
1331{
1333}
1334
1336{
1338}
1339
1341{
1343}
1344
1346{
1348
1349public:
1350 LoopThreadData() = default;
1351 void pushIteration(int index)
1352 {
1353 m_activeLoopStack.push_back(index);
1354 }
1356 {
1357 QT_ASSERT(m_activeLoopStack.size(), return);
1358 m_activeLoopStack.pop_back();
1359 }
1360 int iteration() const
1361 {
1362 QT_ASSERT(m_activeLoopStack.size(), qWarning(
1363 "The referenced loop is not reachable in the running tree. "
1364 "A -1 will be returned which might lead to a crash in the calling code. "
1365 "It is possible that no loop was added to the tree, "
1366 "or the loop is not reachable from where it is referenced."); return -1);
1367 return m_activeLoopStack.last();
1368 }
1369
1370private:
1371 QList<int> m_activeLoopStack;
1372};
1373
1375{
1376public:
1378 QMutexLocker lock(&m_threadDataMutex);
1379 return m_threadDataMap.try_emplace(QThread::currentThread()).first->second;
1380 }
1381
1382 const std::optional<int> m_loopCount = {};
1386 // Use std::map on purpose, so that it doesn't invalidate references on modifications.
1387 // Don't optimize it by using std::unordered_map.
1389};
1390
1392 : m_loopData(new LoopData)
1393{}
1394
1395Loop::Loop(int count, const ValueGetter &valueGetter)
1397{}
1398
1399Loop::Loop(const Condition &condition)
1400 : m_loopData(new LoopData{{}, {}, condition})
1401{}
1402
1403int Loop::iteration() const
1404{
1405 return m_loopData->threadData().iteration();
1406}
1407
1408const void *Loop::valuePtr() const
1409{
1410 return m_loopData->m_valueGetter(iteration());
1411}
1412
1413using StoragePtr = void *;
1414
1416 "The referenced storage is not reachable in the running tree. "
1417 "A nullptr will be returned which might lead to a crash in the calling code. "
1418 "It is possible that no storage was added to the tree, "
1419 "or the storage is not reachable from where it is referenced."_L1;
1420
1422{
1424
1425public:
1427 void pushStorage(StoragePtr storagePtr)
1428 {
1429 m_activeStorageStack.push_back({storagePtr, activeTaskTree()});
1430 }
1432 {
1433 QT_ASSERT(m_activeStorageStack.size(), return);
1434 m_activeStorageStack.pop_back();
1435 }
1437 {
1438 QT_ASSERT(m_activeStorageStack.size(),
1439 qWarning().noquote() << s_activeStorageWarning; return nullptr);
1440 const QPair<StoragePtr, TaskTree *> &top = m_activeStorageStack.last();
1441 QT_ASSERT(top.second == activeTaskTree(),
1442 qWarning().noquote() << s_activeStorageWarning; return nullptr);
1443 return top.first;
1444 }
1445
1446private:
1447 QList<QPair<StoragePtr, TaskTree *>> m_activeStorageStack;
1448};
1449
1451{
1452public:
1454 QMutexLocker lock(&m_threadDataMutex);
1455 return m_threadDataMap.try_emplace(QThread::currentThread()).first->second;
1456 }
1457
1461 // Use std::map on purpose, so that it doesn't invalidate references on modifications.
1462 // Don't optimize it by using std::unordered_map.
1464};
1465
1466StorageBase::StorageBase(const StorageConstructor &ctor, const StorageDestructor &dtor)
1467 : m_storageData(new StorageData{ctor, dtor})
1468{}
1469
1470void *StorageBase::activeStorageVoid() const
1471{
1472 return m_storageData->threadData().activeStorage();
1473}
1474
1475void GroupItem::addChildren(const GroupItems &children)
1476{
1477 QT_ASSERT(m_type == Type::Group || m_type == Type::List,
1478 qWarning("Only Group or List may have children, skipping..."); return);
1479 if (m_type == Type::List) {
1480 m_children.append(children);
1481 return;
1482 }
1483 for (const GroupItem &child : children) {
1484 switch (child.m_type) {
1485 case Type::List:
1486 addChildren(child.m_children);
1487 break;
1488 case Type::Group:
1489 m_children.append(child);
1490 break;
1491 case Type::GroupData:
1492 if (child.m_groupData.m_groupHandler.m_setupHandler) {
1493 QT_ASSERT(!m_groupData.m_groupHandler.m_setupHandler,
1494 qWarning("Group setup handler redefinition, overriding..."));
1495 m_groupData.m_groupHandler.m_setupHandler
1496 = child.m_groupData.m_groupHandler.m_setupHandler;
1497 }
1498 if (child.m_groupData.m_groupHandler.m_doneHandler) {
1499 QT_ASSERT(!m_groupData.m_groupHandler.m_doneHandler,
1500 qWarning("Group done handler redefinition, overriding..."));
1501 m_groupData.m_groupHandler.m_doneHandler
1502 = child.m_groupData.m_groupHandler.m_doneHandler;
1503 m_groupData.m_groupHandler.m_callDoneIf
1504 = child.m_groupData.m_groupHandler.m_callDoneIf;
1505 }
1506 if (child.m_groupData.m_parallelLimit) {
1507 QT_ASSERT(!m_groupData.m_parallelLimit,
1508 qWarning("Group execution mode redefinition, overriding..."));
1509 m_groupData.m_parallelLimit = child.m_groupData.m_parallelLimit;
1510 }
1511 if (child.m_groupData.m_workflowPolicy) {
1512 QT_ASSERT(!m_groupData.m_workflowPolicy,
1513 qWarning("Group workflow policy redefinition, overriding..."));
1514 m_groupData.m_workflowPolicy = child.m_groupData.m_workflowPolicy;
1515 }
1516 if (child.m_groupData.m_loop) {
1517 QT_ASSERT(!m_groupData.m_loop,
1518 qWarning("Group loop redefinition, overriding..."));
1519 m_groupData.m_loop = child.m_groupData.m_loop;
1520 }
1521 break;
1522 case Type::TaskHandler:
1523 QT_ASSERT(child.m_taskHandler.m_createHandler,
1524 qWarning("Task create handler can't be null, skipping..."); return);
1525 m_children.append(child);
1526 break;
1527 case Type::Storage:
1528 // Check for duplicates, as can't have the same storage twice on the same level.
1529 for (const StorageBase &storage : child.m_storageList) {
1530 if (m_storageList.contains(storage)) {
1531 QT_ASSERT(false, qWarning("Can't add the same storage into one Group twice, "
1532 "skipping..."));
1533 continue;
1534 }
1535 m_storageList.append(storage);
1536 }
1537 break;
1538 }
1539 }
1540}
1541
1542/*!
1543 \class Tasking::ExecutableItem
1544 \inheaderfile solutions/tasking/tasktree.h
1545 \inmodule TaskingSolution
1546 \brief Base class for executable task items.
1547 \reentrant
1548
1549 \c ExecutableItem provides an additional interface for items containing executable tasks.
1550 Use withTimeout() to attach a timeout to a task.
1551 Use withLog() to include debugging information about the task startup and the execution result.
1552*/
1553
1554/*!
1555 Attaches \c TimeoutTask to a copy of \c this ExecutableItem, elapsing after \a timeout
1556 in milliseconds, with an optionally provided timeout \a handler, and returns the coupled item.
1557
1558 When the ExecutableItem finishes before \a timeout passes, the returned item finishes
1559 immediately with the task's result. Otherwise, \a handler is invoked (if provided),
1560 the task is canceled, and the returned item finishes with an error.
1561*/
1562Group ExecutableItem::withTimeout(milliseconds timeout,
1563 const std::function<void()> &handler) const
1564{
1565 const auto onSetup = [timeout](milliseconds &timeoutData) { timeoutData = timeout; };
1566 return Group {
1567 parallel,
1569 Group {
1571 handler ? TimeoutTask(onSetup, [handler] { handler(); }, CallDoneIf::Success)
1572 : TimeoutTask(onSetup)
1573 },
1574 *this
1575 };
1576}
1577
1578static QString currentTime() { return QTime::currentTime().toString(Qt::ISODateWithMs); }
1579
1580static QString logHeader(const QString &logName)
1581{
1582 return QString::fromLatin1("TASK TREE LOG [%1] \"%2\"").arg(currentTime(), logName);
1583};
1584
1585/*!
1586 Attaches a custom debug printout to a copy of \c this ExecutableItem,
1587 issued on task startup and after the task is finished, and returns the coupled item.
1588
1589 The debug printout includes a timestamp of the event (start or finish)
1590 and \a logName to identify the specific task in the debug log.
1591
1592 The finish printout contains the additional information whether the execution was
1593 synchronous or asynchronous, its result (the value described by the DoneWith enum),
1594 and the total execution time in milliseconds.
1595*/
1596Group ExecutableItem::withLog(const QString &logName) const
1597{
1598 struct LogStorage
1599 {
1600 time_point<system_clock, nanoseconds> start;
1601 int asyncCount = 0;
1602 };
1603 const Storage<LogStorage> storage;
1604 return Group {
1605 storage,
1606 onGroupSetup([storage, logName] {
1607 storage->start = system_clock::now();
1608 storage->asyncCount = activeTaskTree()->asyncCount();
1609 qDebug().noquote().nospace() << logHeader(logName) << " started.";
1610 }),
1611 *this,
1612 onGroupDone([storage, logName](DoneWith result) {
1613 const auto elapsed = duration_cast<milliseconds>(system_clock::now() - storage->start);
1614 const int asyncCountDiff = activeTaskTree()->asyncCount() - storage->asyncCount;
1615 QT_CHECK(asyncCountDiff >= 0);
1616 const QMetaEnum doneWithEnum = QMetaEnum::fromType<DoneWith>();
1617 const QString syncType = asyncCountDiff ? QString::fromLatin1("asynchronously")
1618 : QString::fromLatin1("synchronously");
1619 qDebug().noquote().nospace() << logHeader(logName) << " finished " << syncType
1620 << " with " << doneWithEnum.valueToKey(int(result))
1621 << " within " << elapsed.count() << "ms.";
1622 })
1623 };
1624}
1625
1626/*!
1627 \fn Group ExecutableItem::operator!(const ExecutableItem &item)
1628
1629 Returns a Group with the DoneResult of \a item negated.
1630
1631 If \a item reports DoneResult::Success, the returned item reports DoneResult::Error.
1632 If \a item reports DoneResult::Error, the returned item reports DoneResult::Success.
1633
1634 The returned item is equivalent to:
1635 \code
1636 Group {
1637 item,
1638 onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); })
1639 }
1640 \endcode
1641
1642 \sa operator&&(), operator||()
1643*/
1645{
1646 return {
1647 item,
1648 onGroupDone([](DoneWith doneWith) { return toDoneResult(doneWith == DoneWith::Error); })
1649 };
1650}
1651
1652/*!
1653 \fn Group ExecutableItem::operator&&(const ExecutableItem &first, const ExecutableItem &second)
1654
1655 Returns a Group with \a first and \a second tasks merged with conjunction.
1656
1657 Both \a first and \a second tasks execute in sequence.
1658 If both tasks report DoneResult::Success, the returned item reports DoneResult::Success.
1659 Otherwise, the returned item reports DoneResult::Error.
1660
1661 The returned item is
1662 \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
1663 if the \a first task reports DoneResult::Error, the \a second task is skipped,
1664 and the returned item reports DoneResult::Error immediately.
1665
1666 The returned item is equivalent to:
1667 \code
1668 Group { stopOnError, first, second }
1669 \endcode
1670
1671 \note Parallel execution of conjunction in a short-circuit manner can be achieved with the
1672 following code: \c {Group { parallel, stopOnError, first, second }}. In this case:
1673 if the \e {first finished} task reports DoneResult::Error,
1674 the \e other task is canceled, and the group reports DoneResult::Error immediately.
1675
1676 \sa operator||(), operator!()
1677*/
1678Group operator&&(const ExecutableItem &first, const ExecutableItem &second)
1679{
1680 return { stopOnError, first, second };
1681}
1682
1683/*!
1684 \fn Group ExecutableItem::operator||(const ExecutableItem &first, const ExecutableItem &second)
1685
1686 Returns a Group with \a first and \a second tasks merged with disjunction.
1687
1688 Both \a first and \a second tasks execute in sequence.
1689 If both tasks report DoneResult::Error, the returned item reports DoneResult::Error.
1690 Otherwise, the returned item reports DoneResult::Success.
1691
1692 The returned item is
1693 \l {https://en.wikipedia.org/wiki/Short-circuit_evaluation}{short-circuiting}:
1694 if the \a first task reports DoneResult::Success, the \a second task is skipped,
1695 and the returned item reports DoneResult::Success immediately.
1696
1697 The returned item is equivalent to:
1698 \code
1699 Group { stopOnSuccess, first, second }
1700 \endcode
1701
1702 \note Parallel execution of disjunction in a short-circuit manner can be achieved with the
1703 following code: \c {Group { parallel, stopOnSuccess, first, second }}. In this case:
1704 if the \e {first finished} task reports DoneResult::Success,
1705 the \e other task is canceled, and the group reports DoneResult::Success immediately.
1706
1707 \sa operator&&(), operator!()
1708*/
1709Group operator||(const ExecutableItem &first, const ExecutableItem &second)
1710{
1711 return { stopOnSuccess, first, second };
1712}
1713
1714/*!
1715 \fn Group ExecutableItem::operator&&(const ExecutableItem &item, DoneResult result)
1716 \overload ExecutableItem::operator&&()
1717
1718 Returns the \a item task if the \a result is DoneResult::Success; otherwise returns
1719 the \a item task with its done result tweaked to DoneResult::Error.
1720
1721 The \c {task && DoneResult::Error} is an eqivalent to tweaking the task's done result
1722 into DoneResult::Error unconditionally.
1723*/
1725{
1726 return { result == DoneResult::Success ? stopOnError : finishAllAndError, item };
1727}
1728
1729/*!
1730 \fn Group ExecutableItem::operator||(const ExecutableItem &item, DoneResult result)
1731 \overload ExecutableItem::operator||()
1732
1733 Returns the \a item task if the \a result is DoneResult::Error; otherwise returns
1734 the \a item task with its done result tweaked to DoneResult::Success.
1735
1736 The \c {task || DoneResult::Success} is an eqivalent to tweaking the task's done result
1737 into DoneResult::Success unconditionally.
1738*/
1740{
1741 return { result == DoneResult::Error ? stopOnError : finishAllAndSuccess, item };
1742}
1743
1744Group ExecutableItem::withCancelImpl(
1745 const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper,
1746 const GroupItems &postCancelRecipe) const
1747{
1748 const Storage<bool> canceledStorage(false);
1749
1750 const auto onSetup = [connectWrapper, canceledStorage](Barrier &barrier) {
1751 connectWrapper(&barrier, [barrierPtr = &barrier, canceled = canceledStorage.activeStorage()] {
1752 *canceled = true;
1753 barrierPtr->advance();
1754 });
1755 };
1756
1757 const auto wasCanceled = [canceledStorage] { return *canceledStorage; };
1758
1759 return {
1761 canceledStorage,
1762 Group {
1763 parallel,
1765 BarrierTask(onSetup) && errorItem,
1766 *this
1767 },
1768 If (wasCanceled) >> Then {
1769 postCancelRecipe
1770 }
1771 };
1772}
1773
1774Group ExecutableItem::withAcceptImpl(
1775 const std::function<void(QObject *, const std::function<void()> &)> &connectWrapper) const
1776{
1777 const auto onSetup = [connectWrapper](Barrier &barrier) {
1778 connectWrapper(&barrier, [barrierPtr = &barrier] { barrierPtr->advance(); });
1779 };
1780 return Group {
1781 parallel,
1782 BarrierTask(onSetup),
1783 *this
1784 };
1785}
1786
1787class TaskTreePrivate;
1788class TaskNode;
1789class RuntimeContainer;
1790class RuntimeIteration;
1791class RuntimeTask;
1792
1794{
1795public:
1797 activateTaskTree(iteration);
1798 activateContext(iteration);
1799 }
1801 activateTaskTree(container);
1802 activateContext(container);
1803 }
1805 for (int i = m_activeStorages.size() - 1; i >= 0; --i) // iterate in reverse order
1806 m_activeStorages[i].m_storageData->threadData().popStorage();
1807 for (int i = m_activeLoops.size() - 1; i >= 0; --i) // iterate in reverse order
1808 m_activeLoops[i].m_loopData->threadData().popIteration();
1809 QT_ASSERT(s_activeTaskTrees.size(), return);
1810 s_activeTaskTrees.pop_back();
1811 }
1812
1813private:
1814 void activateTaskTree(RuntimeIteration *iteration);
1815 void activateTaskTree(RuntimeContainer *container);
1816 void activateContext(RuntimeIteration *iteration);
1817 void activateContext(RuntimeContainer *container);
1818 QList<Loop> m_activeLoops;
1819 QList<StorageBase> m_activeStorages;
1820};
1821
1840
1842{
1844
1845public:
1847 TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
1850 {}
1851
1852 bool isTask() const { return bool(m_taskHandler.m_createHandler); }
1853 int taskCount() const { return isTask() ? 1 : m_container.m_taskCount; }
1854
1857};
1858
1860{
1862
1863public:
1866
1867 void start();
1868 void stop();
1870 void advanceProgress(int byValue);
1871 void emitDone(DoneWith result);
1872 void callSetupHandler(const StorageBase &storage, StoragePtr storagePtr) {
1873 callStorageHandler(storage, storagePtr, &StorageHandler::m_setupHandler);
1874 }
1875 void callDoneHandler(const StorageBase &storage, StoragePtr storagePtr) {
1876 callStorageHandler(storage, storagePtr, &StorageHandler::m_doneHandler);
1877 }
1882 typedef StorageBase::StorageHandler StorageHandler::*HandlerPtr; // ptr to class member
1883 void callStorageHandler(const StorageBase &storage, StoragePtr storagePtr, HandlerPtr ptr)
1884 {
1885 const auto it = m_storageHandlers.constFind(storage);
1886 if (it == m_storageHandlers.constEnd())
1887 return;
1888 const StorageHandler storageHandler = *it;
1889 if (storageHandler.*ptr) {
1890 GuardLocker locker(m_guard);
1891 (storageHandler.*ptr)(storagePtr);
1892 }
1893 }
1894
1895 // Node related methods
1896
1897 // If returned value != Continue, childDone() needs to be called in parent container (in caller)
1898 // in order to unwind properly.
1899 void startTask(const std::shared_ptr<RuntimeTask> &node);
1902
1903 // Container related methods
1904
1907 void childDone(RuntimeIteration *iteration, bool success);
1909 bool invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith);
1911
1912 template <typename Container, typename Handler, typename ...Args,
1913 typename ReturnType = std::invoke_result_t<Handler, Args...>>
1914 ReturnType invokeHandler(Container *container, Handler &&handler, Args &&...args)
1915 {
1916 QT_ASSERT(!m_guard.isLocked(), qWarning("Nested execution of handlers detected."
1917 "This may happen when one task's handler has entered a nested event loop,"
1918 "and other task finished during nested event loop's processing, "
1919 "causing stopping (canceling) the task executing the nested event loop. "
1920 "This includes the case when QCoreApplication::processEvents() was called from "
1921 "the handler. It may also happen when the Barrier task is advanced directly "
1922 "from some other task handler. This will lead to a crash. "
1923 "Avoid event processing during handlers' execution. "
1924 "If it can't be avoided, make sure no other tasks are run in parallel when "
1925 "processing events from the handler."));
1926 ExecutionContextActivator activator(container);
1927 GuardLocker locker(m_guard);
1928 return std::invoke(std::forward<Handler>(handler), std::forward<Args>(args)...);
1929 }
1930
1931 static int effectiveLoopCount(const std::optional<Loop> &loop)
1932 {
1933 return loop && loop->m_loopData->m_loopCount ? *loop->m_loopData->m_loopCount : 1;
1934 }
1935
1936 TaskTree *q = nullptr;
1943 std::shared_ptr<RuntimeTask> m_runtimeRoot; // Keep me last in order to destruct first
1944};
1945
1946static bool initialSuccessBit(WorkflowPolicy workflowPolicy)
1947{
1948 switch (workflowPolicy) {
1952 return true;
1957 return false;
1958 }
1959 QT_CHECK(false);
1960 return false;
1961}
1962
1963static bool isProgressive(RuntimeContainer *container);
1964
1981
1983{
1985
1986public:
1994
2005
2007 bool isStarting() const { return m_startGuard.isLocked(); }
2009 bool updateSuccessBit(bool success);
2012 {
2013 return m_containerNode.m_taskTreePrivate->effectiveLoopCount(m_containerNode.m_loop);
2014 }
2015
2016 const ContainerNode &m_containerNode; // Not owning.
2017 RuntimeTask *m_parentTask = nullptr; // Not owning.
2018 const QList<StoragePtr> m_storages; // Owning.
2019
2020 bool m_successBit = true;
2023
2027 bool m_shouldIterate = true;
2029};
2030
2032{
2033public:
2035 {
2036 if (m_task) {
2037 // Ensures the running task's d'tor doesn't emit done() signal. QTCREATORBUG-30204.
2038 QObject::disconnect(m_task.get(), &TaskInterface::done, nullptr, nullptr);
2039 }
2040 }
2041
2042 const TaskNode &m_taskNode; // Not owning.
2043 RuntimeIteration *m_parentIteration = nullptr; // Not owning.
2047};
2048
2050
2051TaskTreePrivate::TaskTreePrivate(TaskTree *taskTree)
2052 : q(taskTree) {}
2053
2054TaskTreePrivate::~TaskTreePrivate() = default;
2055
2056static bool isProgressive(RuntimeContainer *container)
2057{
2059 return iteration ? iteration->m_isProgressive : true;
2060}
2061
2062void ExecutionContextActivator::activateTaskTree(RuntimeIteration *iteration)
2063{
2064 activateTaskTree(iteration->m_container);
2065}
2066
2067void ExecutionContextActivator::activateTaskTree(RuntimeContainer *container)
2068{
2070}
2071
2072void ExecutionContextActivator::activateContext(RuntimeIteration *iteration)
2073{
2074 std::optional<Loop> loop = iteration->loop();
2075 if (loop) {
2076 loop->m_loopData->threadData().pushIteration(iteration->m_iterationIndex);
2077 m_activeLoops.append(*loop);
2078 }
2079 activateContext(iteration->m_container);
2080}
2081
2082void ExecutionContextActivator::activateContext(RuntimeContainer *container)
2083{
2084 const ContainerNode &containerNode = container->m_containerNode;
2085 for (int i = 0; i < containerNode.m_storageList.size(); ++i) {
2086 const StorageBase &storage = containerNode.m_storageList[i];
2087 if (m_activeStorages.contains(storage))
2088 continue; // Storage shadowing: The storage is already active, skipping it...
2089 m_activeStorages.append(storage);
2090 storage.m_storageData->threadData().pushStorage(container->m_storages.value(i));
2091 }
2092 // Go to the parent after activating this storages so that storage shadowing works
2093 // in the direction from child to parent root.
2094 if (container->parentIteration())
2095 activateContext(container->parentIteration());
2096}
2097
2099{
2100 QT_ASSERT(m_root, return);
2101 QT_ASSERT(!m_runtimeRoot, return);
2102 m_asyncCount = 0;
2103 m_progressValue = 0;
2104 {
2105 GuardLocker locker(m_guard);
2106 emit q->started();
2107 emit q->asyncCountChanged(m_asyncCount);
2108 emit q->progressValueChanged(m_progressValue);
2109 }
2110 // TODO: check storage handlers for not existing storages in tree
2111 for (auto it = m_storageHandlers.cbegin(); it != m_storageHandlers.cend(); ++it) {
2112 QT_ASSERT(m_storages.contains(it.key()), qWarning("The registered storage doesn't "
2113 "exist in task tree. Its handlers will never be called."));
2114 }
2115 m_runtimeRoot.reset(new RuntimeTask{*m_root});
2116 startTask(m_runtimeRoot);
2118}
2119
2121{
2122 QT_ASSERT(m_root, return);
2123 if (!m_runtimeRoot)
2124 return;
2125 stopTask(m_runtimeRoot.get());
2126 m_runtimeRoot.reset();
2128}
2129
2131{
2132 if (!m_runtimeRoot)
2133 return;
2134 ++m_asyncCount;
2135 GuardLocker locker(m_guard);
2136 emit q->asyncCountChanged(m_asyncCount);
2137}
2138
2140{
2141 if (byValue == 0)
2142 return;
2143 QT_CHECK(byValue > 0);
2144 QT_CHECK(m_progressValue + byValue <= m_root->taskCount());
2145 m_progressValue += byValue;
2146 GuardLocker locker(m_guard);
2147 emit q->progressValueChanged(m_progressValue);
2148}
2149
2151{
2152 QT_CHECK(m_progressValue == m_root->taskCount());
2153 GuardLocker locker(m_guard);
2154 emit q->done(result);
2155}
2156
2157RuntimeIteration::RuntimeIteration(int index, RuntimeContainer *container)
2158 : m_iterationIndex(index)
2159 , m_isProgressive(index < container->progressiveLoopCount() && isProgressive(container))
2160 , m_container(container)
2161{}
2162
2164{
2165 return m_container->m_containerNode.m_loop;
2166}
2167
2169{
2170 const auto it = std::find_if(m_children.cbegin(), m_children.cend(), [task](const auto &ptr) {
2171 return ptr.get() == task;
2172 });
2173 if (it != m_children.cend())
2174 m_children.erase(it);
2175}
2176
2178 const GroupItems &children)
2179{
2180 std::vector<TaskNode> result;
2181 result.reserve(children.size());
2182 for (const GroupItem &child : children)
2183 result.emplace_back(taskTreePrivate, child);
2184 return result;
2185}
2186
2188 : m_taskTreePrivate(taskTreePrivate)
2190 , m_parallelLimit(task.m_groupData.m_parallelLimit.value_or(1))
2191 , m_workflowPolicy(task.m_groupData.m_workflowPolicy.value_or(WorkflowPolicy::StopOnError))
2196 [](int r, const TaskNode &n) { return r + n.taskCount(); })
2198{
2199 for (const StorageBase &storage : m_storageList)
2200 m_taskTreePrivate->m_storages << storage;
2201}
2202
2203QList<StoragePtr> RuntimeContainer::createStorages(const ContainerNode &container)
2204{
2205 QList<StoragePtr> storages;
2206 for (const StorageBase &storage : container.m_storageList) {
2207 StoragePtr storagePtr = storage.m_storageData->m_constructor();
2208 storages.append(storagePtr);
2209 container.m_taskTreePrivate->callSetupHandler(storage, storagePtr);
2210 }
2211 return storages;
2212}
2213
2218
2234
2236{
2237 for (auto it = m_iterations.cbegin(); it != m_iterations.cend(); ) {
2238 if (it->get()->m_doneCount == int(m_containerNode.m_children.size()))
2239 it = m_iterations.erase(it);
2240 else
2241 ++it;
2242 }
2243}
2244
2246{
2247 RuntimeTask *parentTask = container->m_parentTask;
2248 if (parentTask->m_setupResult == SetupResult::Continue)
2249 startChildren(container);
2250 if (parentTask->m_setupResult == SetupResult::Continue)
2251 return;
2252
2253 const bool bit = container->updateSuccessBit(parentTask->m_setupResult == SetupResult::StopWithSuccess);
2254 RuntimeIteration *parentIteration = container->parentIteration();
2255 QT_CHECK(parentTask);
2256 const bool result = invokeDoneHandler(container, bit ? DoneWith::Success : DoneWith::Error);
2257 parentTask->m_setupResult = toSetupResult(result);
2258 if (parentIteration) {
2259 parentIteration->removeChild(parentTask);
2260 if (!parentIteration->m_container->isStarting())
2261 childDone(parentIteration, result);
2262 } else {
2263 QT_CHECK(m_runtimeRoot.get() == parentTask);
2264 m_runtimeRoot.reset();
2266 }
2267}
2268
2270{
2271 const ContainerNode &containerNode = container->m_containerNode;
2272 const int childCount = int(containerNode.m_children.size());
2273
2274 if (container->m_iterationCount == 0) {
2275 if (container->m_shouldIterate && !invokeLoopHandler(container)) {
2276 if (isProgressive(container))
2277 advanceProgress(containerNode.m_taskCount);
2279 return;
2280 }
2281 container->m_iterations.emplace_back(
2282 std::make_unique<RuntimeIteration>(container->m_iterationCount, container));
2283 ++container->m_iterationCount;
2284 }
2285
2286 GuardLocker locker(container->m_startGuard);
2287
2288 while (containerNode.m_parallelLimit == 0
2289 || container->m_runningChildren < containerNode.m_parallelLimit) {
2291 if (container->m_nextToStart == childCount) {
2292 if (invokeLoopHandler(container)) {
2293 container->m_nextToStart = 0;
2294 container->m_iterations.emplace_back(
2295 std::make_unique<RuntimeIteration>(container->m_iterationCount, container));
2296 ++container->m_iterationCount;
2297 } else if (container->m_iterations.empty()) {
2299 return;
2300 } else {
2301 return;
2302 }
2303 }
2304 if (containerNode.m_children.size() == 0) // Empty loop body.
2305 continue;
2306
2307 RuntimeIteration *iteration = container->m_iterations.back().get();
2308 const std::shared_ptr<RuntimeTask> task(
2309 new RuntimeTask{containerNode.m_children.at(container->m_nextToStart), iteration});
2310 iteration->m_children.emplace_back(task);
2311 ++container->m_runningChildren;
2312 ++container->m_nextToStart;
2313
2314 startTask(task);
2315 if (task->m_setupResult == SetupResult::Continue)
2316 continue;
2317
2318 task->m_parentIteration->removeChild(task.get());
2319 childDone(iteration, task->m_setupResult == SetupResult::StopWithSuccess);
2321 return;
2322 }
2323}
2324
2325void TaskTreePrivate::childDone(RuntimeIteration *iteration, bool success)
2326{
2327 RuntimeContainer *container = iteration->m_container;
2328 const WorkflowPolicy &workflowPolicy = container->m_containerNode.m_workflowPolicy;
2329 const bool shouldStop = workflowPolicy == WorkflowPolicy::StopOnSuccessOrError
2330 || (workflowPolicy == WorkflowPolicy::StopOnSuccess && success)
2331 || (workflowPolicy == WorkflowPolicy::StopOnError && !success);
2332 ++iteration->m_doneCount;
2333 --container->m_runningChildren;
2334 const bool updatedSuccess = container->updateSuccessBit(success);
2335 container->m_parentTask->m_setupResult = shouldStop ? toSetupResult(updatedSuccess) : SetupResult::Continue;
2336 if (shouldStop)
2337 stopContainer(container);
2338
2339 if (container->isStarting())
2340 return;
2341 continueContainer(container);
2342}
2343
2345{
2346 const ContainerNode &containerNode = container->m_containerNode;
2347 for (auto &iteration : container->m_iterations) {
2348 for (auto &child : iteration->m_children) {
2349 ++iteration->m_doneCount;
2350 stopTask(child.get());
2351 }
2352
2353 if (iteration->m_isProgressive) {
2354 int skippedTaskCount = 0;
2355 for (int i = iteration->m_doneCount; i < int(containerNode.m_children.size()); ++i)
2356 skippedTaskCount += containerNode.m_children.at(i).taskCount();
2357 advanceProgress(skippedTaskCount);
2358 }
2359 }
2360 const int skippedIterations = container->progressiveLoopCount() - container->m_iterationCount;
2361 if (skippedIterations > 0) {
2363 * skippedIterations);
2364 }
2365}
2366
2367static bool shouldCall(CallDoneIf callDoneIf, DoneWith result)
2368{
2369 if (result == DoneWith::Success)
2370 return callDoneIf != CallDoneIf::Error;
2371 return callDoneIf != CallDoneIf::Success;
2372}
2373
2375{
2376 DoneResult result = toDoneResult(doneWith);
2377 const GroupItem::GroupHandler &groupHandler = container->m_containerNode.m_groupHandler;
2378 if (groupHandler.m_doneHandler && shouldCall(groupHandler.m_callDoneIf, doneWith))
2379 result = invokeHandler(container, groupHandler.m_doneHandler, doneWith);
2381 return result == DoneResult::Success;
2382}
2383
2385{
2386 if (container->m_shouldIterate) {
2387 const LoopData *loopData = container->m_containerNode.m_loop->m_loopData.get();
2388 if (loopData->m_loopCount) {
2389 container->m_shouldIterate = container->m_iterationCount < loopData->m_loopCount;
2390 } else if (loopData->m_condition) {
2391 container->m_shouldIterate = invokeHandler(container, loopData->m_condition,
2392 container->m_iterationCount);
2393 }
2394 }
2395 return container->m_shouldIterate;
2396}
2397
2398void TaskTreePrivate::startTask(const std::shared_ptr<RuntimeTask> &node)
2399{
2400 if (!node->m_taskNode.isTask()) {
2401 const ContainerNode &containerNode = node->m_taskNode.m_container;
2402 node->m_container.emplace(containerNode, node.get());
2403 RuntimeContainer *container = &*node->m_container;
2404 if (containerNode.m_groupHandler.m_setupHandler) {
2405 container->m_parentTask->m_setupResult = invokeHandler(container, containerNode.m_groupHandler.m_setupHandler);
2407 if (isProgressive(container))
2408 advanceProgress(containerNode.m_taskCount);
2409 // Non-Continue SetupResult takes precedence over the workflow policy.
2411 }
2412 }
2413 continueContainer(container);
2414 return;
2415 }
2416
2417 const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
2418 node->m_task.reset(handler.m_createHandler());
2419 node->m_setupResult = handler.m_setupHandler
2420 ? invokeHandler(node->m_parentIteration, handler.m_setupHandler, *node->m_task.get())
2421 : SetupResult::Continue;
2422 if (node->m_setupResult != SetupResult::Continue) {
2423 if (node->m_parentIteration->m_isProgressive)
2425 node->m_parentIteration->removeChild(node.get());
2426 return;
2427 }
2428 QObject::connect(node->m_task.get(), &TaskInterface::done,
2429 q, [this, node](DoneResult doneResult) {
2430 const bool result = invokeTaskDoneHandler(node.get(), toDoneWith(doneResult));
2431 node->m_setupResult = toSetupResult(result);
2432 QObject::disconnect(node->m_task.get(), &TaskInterface::done, q, nullptr);
2433 node->m_task.release()->deleteLater();
2434 RuntimeIteration *parentIteration = node->m_parentIteration;
2435 if (parentIteration->m_container->isStarting())
2436 return;
2437
2438 parentIteration->removeChild(node.get());
2439 childDone(parentIteration, result);
2440 bumpAsyncCount();
2441 });
2442 node->m_task->start();
2443}
2444
2446{
2447 if (!node->m_task) {
2448 if (!node->m_container)
2449 return;
2450 stopContainer(&*node->m_container);
2451 node->m_container->updateSuccessBit(false);
2452 invokeDoneHandler(&*node->m_container, DoneWith::Cancel);
2453 return;
2454 }
2455
2457 node->m_task.reset();
2458}
2459
2461{
2462 DoneResult result = toDoneResult(doneWith);
2463 const GroupItem::TaskHandler &handler = node->m_taskNode.m_taskHandler;
2464 if (handler.m_doneHandler && shouldCall(handler.m_callDoneIf, doneWith)) {
2465 result = invokeHandler(node->m_parentIteration,
2466 handler.m_doneHandler, *node->m_task.get(), doneWith);
2467 }
2470 return result == DoneResult::Success;
2471}
2472
2473/*!
2474 \class Tasking::TaskTree
2475 \inheaderfile solutions/tasking/tasktree.h
2476 \inmodule TaskingSolution
2477 \brief The TaskTree class runs an async task tree structure defined in a declarative way.
2478 \reentrant
2479
2480 Use the Tasking namespace to build extensible, declarative task tree
2481 structures that contain possibly asynchronous tasks, such as QProcess,
2482 NetworkQuery, or ConcurrentCall<ReturnType>. TaskTree structures enable you
2483 to create a sophisticated mixture of a parallel or sequential flow of tasks
2484 in the form of a tree and to run it any time later.
2485
2486 \section1 Root Element and Tasks
2487
2488 The TaskTree has a mandatory Group root element, which may contain
2489 any number of tasks of various types, such as QProcessTask, NetworkQueryTask,
2490 or ConcurrentCallTask<ReturnType>:
2491
2492 \code
2493 using namespace Tasking;
2494
2495 const Group root {
2496 QProcessTask(...),
2497 NetworkQueryTask(...),
2498 ConcurrentCallTask<int>(...)
2499 };
2500
2501 TaskTree *taskTree = new TaskTree(root);
2502 connect(taskTree, &TaskTree::done, ...); // finish handler
2503 taskTree->start();
2504 \endcode
2505
2506 The task tree above has a top level element of the Group type that contains
2507 tasks of the QProcessTask, NetworkQueryTask, and ConcurrentCallTask<int> type.
2508 After taskTree->start() is called, the tasks are run in a chain, starting
2509 with QProcessTask. When the QProcessTask finishes successfully, the NetworkQueryTask
2510 task is started. Finally, when the network task finishes successfully, the
2511 ConcurrentCallTask<int> task is started.
2512
2513 When the last running task finishes with success, the task tree is considered
2514 to have run successfully and the done() signal is emitted with DoneWith::Success.
2515 When a task finishes with an error, the execution of the task tree is stopped
2516 and the remaining tasks are skipped. The task tree finishes with an error and
2517 sends the TaskTree::done() signal with DoneWith::Error.
2518
2519 \section1 Groups
2520
2521 The parent of the Group sees it as a single task. Like other tasks,
2522 the group can be started and it can finish with success or an error.
2523 The Group elements can be nested to create a tree structure:
2524
2525 \code
2526 const Group root {
2527 Group {
2528 parallel,
2529 QProcessTask(...),
2530 ConcurrentCallTask<int>(...)
2531 },
2532 NetworkQueryTask(...)
2533 };
2534 \endcode
2535
2536 The example above differs from the first example in that the root element has
2537 a subgroup that contains the QProcessTask and ConcurrentCallTask<int>. The subgroup is a
2538 sibling element of the NetworkQueryTask in the root. The subgroup contains an
2539 additional \e parallel element that instructs its Group to execute its tasks
2540 in parallel.
2541
2542 So, when the tree above is started, the QProcessTask and ConcurrentCallTask<int> start
2543 immediately and run in parallel. Since the root group doesn't contain a
2544 \e parallel element, its direct child tasks are run in sequence. Thus, the
2545 NetworkQueryTask starts when the whole subgroup finishes. The group is
2546 considered as finished when all its tasks have finished. The order in which
2547 the tasks finish is not relevant.
2548
2549 So, depending on which task lasts longer (QProcessTask or ConcurrentCallTask<int>), the
2550 following scenarios can take place:
2551
2552 \table
2553 \header
2554 \li Scenario 1
2555 \li Scenario 2
2556 \row
2557 \li Root Group starts
2558 \li Root Group starts
2559 \row
2560 \li Sub Group starts
2561 \li Sub Group starts
2562 \row
2563 \li QProcessTask starts
2564 \li QProcessTask starts
2565 \row
2566 \li ConcurrentCallTask<int> starts
2567 \li ConcurrentCallTask<int> starts
2568 \row
2569 \li ...
2570 \li ...
2571 \row
2572 \li \b {QProcessTask finishes}
2573 \li \b {ConcurrentCallTask<int> finishes}
2574 \row
2575 \li ...
2576 \li ...
2577 \row
2578 \li \b {ConcurrentCallTask<int> finishes}
2579 \li \b {QProcessTask finishes}
2580 \row
2581 \li Sub Group finishes
2582 \li Sub Group finishes
2583 \row
2584 \li NetworkQueryTask starts
2585 \li NetworkQueryTask starts
2586 \row
2587 \li ...
2588 \li ...
2589 \row
2590 \li NetworkQueryTask finishes
2591 \li NetworkQueryTask finishes
2592 \row
2593 \li Root Group finishes
2594 \li Root Group finishes
2595 \endtable
2596
2597 The differences between the scenarios are marked with bold. Three dots mean
2598 that an unspecified amount of time passes between previous and next events
2599 (a task or tasks continue to run). No dots between events
2600 means that they occur synchronously.
2601
2602 The presented scenarios assume that all tasks run successfully. If a task
2603 fails during execution, the task tree finishes with an error. In particular,
2604 when QProcessTask finishes with an error while ConcurrentCallTask<int> is still being executed,
2605 the ConcurrentCallTask<int> is automatically canceled, the subgroup finishes with an error,
2606 the NetworkQueryTask is skipped, and the tree finishes with an error.
2607
2608 \section1 Task Types
2609
2610 Each task type is associated with its corresponding task class that executes
2611 the task. For example, a QProcessTask inside a task tree is associated with
2612 the QProcess class that executes the process. The associated objects are
2613 automatically created, started, and destructed exclusively by the task tree
2614 at the appropriate time.
2615
2616 If a root group consists of five sequential QProcessTask tasks, and the task tree
2617 executes the group, it creates an instance of QProcess for the first
2618 QProcessTask and starts it. If the QProcess instance finishes successfully,
2619 the task tree destructs it and creates a new QProcess instance for the
2620 second QProcessTask, and so on. If the first task finishes with an error, the task
2621 tree stops creating QProcess instances, and the root group finishes with an
2622 error.
2623
2624 The following table shows examples of task types and their corresponding task
2625 classes:
2626
2627 \table
2628 \header
2629 \li Task Type (Tasking Namespace)
2630 \li Associated Task Class
2631 \li Brief Description
2632 \row
2633 \li QProcessTask
2634 \li QProcess
2635 \li Starts process.
2636 \row
2637 \li ConcurrentCallTask<ReturnType>
2638 \li Tasking::ConcurrentCall<ReturnType>
2639 \li Starts asynchronous task, runs in separate thread.
2640 \row
2641 \li TaskTreeTask
2642 \li Tasking::TaskTree
2643 \li Starts nested task tree.
2644 \row
2645 \li NetworkQueryTask
2646 \li NetworkQuery
2647 \li Starts network download.
2648 \endtable
2649
2650 \section1 Task Handlers
2651
2652 Use Task handlers to set up a task for execution and to enable reading
2653 the output data from the task when it finishes with success or an error.
2654
2655 \section2 Task's Start Handler
2656
2657 When a corresponding task class object is created and before it's started,
2658 the task tree invokes an optionally user-provided setup handler. The setup
2659 handler should always take a \e reference to the associated task class object:
2660
2661 \code
2662 const auto onSetup = [](QProcess &process) {
2663 process.setProgram("sleep");
2664 process.setArguments({"3"});
2665 };
2666 const Group root {
2667 QProcessTask(onSetup)
2668 };
2669 \endcode
2670
2671 You can modify the passed QProcess in the setup handler, so that the task
2672 tree can start the process according to your configuration.
2673 You should not call \c {process.start();} in the setup handler,
2674 as the task tree calls it when needed. The setup handler is optional. When used,
2675 it must be the first argument of the task's constructor.
2676
2677 Optionally, the setup handler may return a SetupResult. The returned
2678 SetupResult influences the further start behavior of a given task. The
2679 possible values are:
2680
2681 \table
2682 \header
2683 \li SetupResult Value
2684 \li Brief Description
2685 \row
2686 \li Continue
2687 \li The task will be started normally. This is the default behavior when the
2688 setup handler doesn't return SetupResult (that is, its return type is
2689 void).
2690 \row
2691 \li StopWithSuccess
2692 \li The task won't be started and it will report success to its parent.
2693 \row
2694 \li StopWithError
2695 \li The task won't be started and it will report an error to its parent.
2696 \endtable
2697
2698 This is useful for running a task only when a condition is met and the data
2699 needed to evaluate this condition is not known until previously started tasks
2700 finish. In this way, the setup handler dynamically decides whether to start the
2701 corresponding task normally or skip it and report success or an error.
2702 For more information about inter-task data exchange, see \l Storage.
2703
2704 \section2 Task's Done Handler
2705
2706 When a running task finishes, the task tree invokes an optionally provided done handler.
2707 The handler should take a \c const \e reference to the associated task class object:
2708
2709 \code
2710 const auto onSetup = [](QProcess &process) {
2711 process.setProgram("sleep");
2712 process.setArguments({"3"});
2713 };
2714 const auto onDone = [](const QProcess &process, DoneWith result) {
2715 if (result == DoneWith::Success)
2716 qDebug() << "Success" << process.cleanedStdOut();
2717 else
2718 qDebug() << "Failure" << process.cleanedStdErr();
2719 };
2720 const Group root {
2721 QProcessTask(onSetup, onDone)
2722 };
2723 \endcode
2724
2725 The done handler may collect output data from QProcess, and store it
2726 for further processing or perform additional actions.
2727
2728 \note If the task setup handler returns StopWithSuccess or StopWithError,
2729 the done handler is not invoked.
2730
2731 \section1 Group Handlers
2732
2733 Similarly to task handlers, group handlers enable you to set up a group to
2734 execute and to apply more actions when the whole group finishes with
2735 success or an error.
2736
2737 \section2 Group's Start Handler
2738
2739 The task tree invokes the group start handler before it starts the child
2740 tasks. The group handler doesn't take any arguments:
2741
2742 \code
2743 const auto onSetup = [] {
2744 qDebug() << "Entering the group";
2745 };
2746 const Group root {
2747 onGroupSetup(onSetup),
2748 QProcessTask(...)
2749 };
2750 \endcode
2751
2752 The group setup handler is optional. To define a group setup handler, add an
2753 onGroupSetup() element to a group. The argument of onGroupSetup() is a user
2754 handler. If you add more than one onGroupSetup() element to a group, an assert
2755 is triggered at runtime that includes an error message.
2756
2757 Like the task's start handler, the group start handler may return SetupResult.
2758 The returned SetupResult value affects the start behavior of the
2759 whole group. If you do not specify a group start handler or its return type
2760 is void, the default group's action is SetupResult::Continue, so that all
2761 tasks are started normally. Otherwise, when the start handler returns
2762 SetupResult::StopWithSuccess or SetupResult::StopWithError, the tasks are not
2763 started (they are skipped) and the group itself reports success or failure,
2764 depending on the returned value, respectively.
2765
2766 \code
2767 const Group root {
2768 onGroupSetup([] { qDebug() << "Root setup"; }),
2769 Group {
2770 onGroupSetup([] { qDebug() << "Group 1 setup"; return SetupResult::Continue; }),
2771 QProcessTask(...) // Process 1
2772 },
2773 Group {
2774 onGroupSetup([] { qDebug() << "Group 2 setup"; return SetupResult::StopWithSuccess; }),
2775 QProcessTask(...) // Process 2
2776 },
2777 Group {
2778 onGroupSetup([] { qDebug() << "Group 3 setup"; return SetupResult::StopWithError; }),
2779 QProcessTask(...) // Process 3
2780 },
2781 QProcessTask(...) // Process 4
2782 };
2783 \endcode
2784
2785 In the above example, all subgroups of a root group define their setup handlers.
2786 The following scenario assumes that all started processes finish with success:
2787
2788 \table
2789 \header
2790 \li Scenario
2791 \li Comment
2792 \row
2793 \li Root Group starts
2794 \li Doesn't return SetupResult, so its tasks are executed.
2795 \row
2796 \li Group 1 starts
2797 \li Returns Continue, so its tasks are executed.
2798 \row
2799 \li Process 1 starts
2800 \li
2801 \row
2802 \li ...
2803 \li ...
2804 \row
2805 \li Process 1 finishes (success)
2806 \li
2807 \row
2808 \li Group 1 finishes (success)
2809 \li
2810 \row
2811 \li Group 2 starts
2812 \li Returns StopWithSuccess, so Process 2 is skipped and Group 2 reports
2813 success.
2814 \row
2815 \li Group 2 finishes (success)
2816 \li
2817 \row
2818 \li Group 3 starts
2819 \li Returns StopWithError, so Process 3 is skipped and Group 3 reports
2820 an error.
2821 \row
2822 \li Group 3 finishes (error)
2823 \li
2824 \row
2825 \li Root Group finishes (error)
2826 \li Group 3, which is a direct child of the root group, finished with an
2827 error, so the root group stops executing, skips Process 4, which has
2828 not started yet, and reports an error.
2829 \endtable
2830
2831 \section2 Groups's Done Handler
2832
2833 A Group's done handler is executed after the successful or failed execution of its tasks.
2834 The final value reported by the group depends on its \l {Workflow Policy}.
2835 The handler can apply other necessary actions.
2836 The done handler is defined inside the onGroupDone() element of a group.
2837 It may take the optional DoneWith argument, indicating the successful or failed execution:
2838
2839 \code
2840 const Group root {
2841 onGroupSetup([] { qDebug() << "Root setup"; }),
2842 QProcessTask(...),
2843 onGroupDone([](DoneWith result) {
2844 if (result == DoneWith::Success)
2845 qDebug() << "Root finished with success";
2846 else
2847 qDebug() << "Root finished with an error";
2848 })
2849 };
2850 \endcode
2851
2852 The group done handler is optional. If you add more than one onGroupDone() to a group,
2853 an assert is triggered at runtime that includes an error message.
2854
2855 \note Even if the group setup handler returns StopWithSuccess or StopWithError,
2856 the group's done handler is invoked. This behavior differs from that of task done handler
2857 and might change in the future.
2858
2859 \section1 Other Group Elements
2860
2861 A group can contain other elements that describe the processing flow, such as
2862 the execution mode or workflow policy. It can also contain storage elements
2863 that are responsible for collecting and sharing custom common data gathered
2864 during group execution.
2865
2866 \section2 Execution Mode
2867
2868 The execution mode element in a Group specifies how the direct child tasks of
2869 the Group are started. The most common execution modes are \l sequential and
2870 \l parallel. It's also possible to specify the limit of tasks running
2871 in parallel by using the parallelLimit() function.
2872
2873 In all execution modes, a group starts tasks in the oder in which they appear.
2874
2875 If a child of a group is also a group, the child group runs its tasks
2876 according to its own execution mode.
2877
2878 \section2 Workflow Policy
2879
2880 The workflow policy element in a Group specifies how the group should behave
2881 when any of its \e direct child's tasks finish. For a detailed description of possible
2882 policies, refer to WorkflowPolicy.
2883
2884 If a child of a group is also a group, the child group runs its tasks
2885 according to its own workflow policy.
2886
2887 \section2 Storage
2888
2889 Use the \l {Tasking::Storage} {Storage} element to exchange information between tasks.
2890 Especially, in the sequential execution mode, when a task needs data from another,
2891 already finished task, before it can start. For example, a task tree that copies data by reading
2892 it from a source and writing it to a destination might look as follows:
2893
2894 \code
2895 static QByteArray load(const QString &fileName) { ... }
2896 static void save(const QString &fileName, const QByteArray &array) { ... }
2897
2898 static Group copyRecipe(const QString &source, const QString &destination)
2899 {
2900 struct CopyStorage { // [1] custom inter-task struct
2901 QByteArray content; // [2] custom inter-task data
2902 };
2903
2904 // [3] instance of custom inter-task struct manageable by task tree
2905 const Storage<CopyStorage> storage;
2906
2907 const auto onLoaderSetup = [source](ConcurrentCall<QByteArray> &async) {
2908 async.setConcurrentCallData(&load, source);
2909 };
2910 // [4] runtime: task tree activates the instance from [7] before invoking handler
2911 const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &async) {
2912 storage->content = async.result(); // [5] loader stores the result in storage
2913 };
2914
2915 // [4] runtime: task tree activates the instance from [7] before invoking handler
2916 const auto onSaverSetup = [storage, destination](ConcurrentCall<void> &async) {
2917 const QByteArray content = storage->content; // [6] saver takes data from storage
2918 async.setConcurrentCallData(&save, destination, content);
2919 };
2920 const auto onSaverDone = [](const ConcurrentCall<void> &async) {
2921 qDebug() << "Save done successfully";
2922 };
2923
2924 const Group root {
2925 // [7] runtime: task tree creates an instance of CopyStorage when root is entered
2926 storage,
2927 ConcurrentCallTask<QByteArray>(onLoaderSetup, onLoaderDone, CallDoneIf::Success),
2928 ConcurrentCallTask<void>(onSaverSetup, onSaverDone, CallDoneIf::Success)
2929 };
2930 return root;
2931 }
2932
2933 const QString source = ...;
2934 const QString destination = ...;
2935 TaskTree taskTree(copyRecipe(source, destination));
2936 connect(&taskTree, &TaskTree::done,
2937 &taskTree, [](DoneWith result) {
2938 if (result == DoneWith::Success)
2939 qDebug() << "The copying finished successfully.";
2940 });
2941 tasktree.start();
2942 \endcode
2943
2944 In the example above, the inter-task data consists of a QByteArray content
2945 variable [2] enclosed in a \c CopyStorage custom struct [1]. If the loader
2946 finishes successfully, it stores the data in a \c CopyStorage::content
2947 variable [5]. The saver then uses the variable to configure the saving task [6].
2948
2949 To enable a task tree to manage the \c CopyStorage struct, an instance of
2950 \l {Tasking::Storage} {Storage}<\c CopyStorage> is created [3]. If a copy of this object is
2951 inserted as the group's child item [7], an instance of the \c CopyStorage struct is
2952 created dynamically when the task tree enters this group. When the task
2953 tree leaves this group, the existing instance of the \c CopyStorage struct is
2954 destructed as it's no longer needed.
2955
2956 If several task trees holding a copy of the common
2957 \l {Tasking::Storage} {Storage}<\c CopyStorage> instance run simultaneously
2958 (including the case when the task trees are run in different threads),
2959 each task tree contains its own copy of the \c CopyStorage struct.
2960
2961 You can access \c CopyStorage from any handler in the group with a storage object.
2962 This includes all handlers of all descendant tasks of the group with
2963 a storage object. To access the custom struct in a handler, pass the
2964 copy of the \l {Tasking::Storage} {Storage}<\c CopyStorage> object to the handler
2965 (for example, in a lambda capture) [4].
2966
2967 When the task tree invokes a handler in a subtree containing the storage [7],
2968 the task tree activates its own \c CopyStorage instance inside the
2969 \l {Tasking::Storage} {Storage}<\c CopyStorage> object. Therefore, the \c CopyStorage struct
2970 may be accessed only from within the handler body. To access the currently active
2971 \c CopyStorage from within \l {Tasking::Storage} {Storage}<\c CopyStorage>, use the
2972 \l {Tasking::Storage::operator->()} {Storage::operator->()},
2973 \l {Tasking::Storage::operator*()} {Storage::operator*()}, or Storage::activeStorage() method.
2974
2975 The following list summarizes how to employ a Storage object into the task
2976 tree:
2977 \list 1
2978 \li Define the custom structure \c MyStorage with custom data [1], [2]
2979 \li Create an instance of the \l {Tasking::Storage} {Storage}<\c MyStorage> storage [3]
2980 \li Pass the \l {Tasking::Storage} {Storage}<\c MyStorage> instance to handlers [4]
2981 \li Access the \c MyStorage instance in handlers [5], [6]
2982 \li Insert the \l {Tasking::Storage} {Storage}<\c MyStorage> instance into a group [7]
2983 \endlist
2984
2985 \section1 TaskTree class
2986
2987 TaskTree executes the tree structure of asynchronous tasks according to the
2988 recipe described by the Group root element.
2989
2990 As TaskTree is also an asynchronous task, it can be a part of another TaskTree.
2991 To place a nested TaskTree inside another TaskTree, insert the TaskTreeTask
2992 element into another Group element.
2993
2994 TaskTree reports progress of completed tasks when running. The progress value
2995 is increased when a task finishes or is skipped or canceled.
2996 When TaskTree is finished and the TaskTree::done() signal is emitted,
2997 the current value of the progress equals the maximum progress value.
2998 Maximum progress equals the total number of asynchronous tasks in a tree.
2999 A nested TaskTree is counted as a single task, and its child tasks are not
3000 counted in the top level tree. Groups themselves are not counted as tasks,
3001 but their tasks are counted. \l {Tasking::Sync} {Sync} tasks are not asynchronous,
3002 so they are not counted as tasks.
3003
3004 To set additional initial data for the running tree, modify the storage
3005 instances in a tree when it creates them by installing a storage setup
3006 handler:
3007
3008 \code
3009 Storage<CopyStorage> storage;
3010 const Group root = ...; // storage placed inside root's group and inside handlers
3011 TaskTree taskTree(root);
3012 auto initStorage = [](CopyStorage &storage) {
3013 storage.content = "initial content";
3014 };
3015 taskTree.onStorageSetup(storage, initStorage);
3016 taskTree.start();
3017 \endcode
3018
3019 When the running task tree creates a \c CopyStorage instance, and before any
3020 handler inside a tree is called, the task tree calls the initStorage handler,
3021 to enable setting up initial data of the storage, unique to this particular
3022 run of taskTree.
3023
3024 Similarly, to collect some additional result data from the running tree,
3025 read it from storage instances in the tree when they are about to be
3026 destroyed. To do this, install a storage done handler:
3027
3028 \code
3029 Storage<CopyStorage> storage;
3030 const Group root = ...; // storage placed inside root's group and inside handlers
3031 TaskTree taskTree(root);
3032 auto collectStorage = [](const CopyStorage &storage) {
3033 qDebug() << "final content" << storage.content;
3034 };
3035 taskTree.onStorageDone(storage, collectStorage);
3036 taskTree.start();
3037 \endcode
3038
3039 When the running task tree is about to destroy a \c CopyStorage instance, the
3040 task tree calls the collectStorage handler, to enable reading the final data
3041 from the storage, unique to this particular run of taskTree.
3042
3043 \section1 Task Adapters
3044
3045 To extend a TaskTree with a new task type, implement a simple adapter class
3046 derived from the TaskAdapter class template. The following class is an
3047 adapter for a single shot timer, which may be considered as a new asynchronous task:
3048
3049 \code
3050 class TimerTaskAdapter : public TaskAdapter<QTimer>
3051 {
3052 public:
3053 TimerTaskAdapter() {
3054 task()->setSingleShot(true);
3055 task()->setInterval(1000);
3056 connect(task(), &QTimer::timeout, this, [this] { emit done(DoneResult::Success); });
3057 }
3058 private:
3059 void start() final { task()->start(); }
3060 };
3061
3062 using TimerTask = CustomTask<TimerTaskAdapter>;
3063 \endcode
3064
3065 You must derive the custom adapter from the TaskAdapter class template
3066 instantiated with a template parameter of the class implementing a running
3067 task. The code above uses QTimer to run the task. This class appears
3068 later as an argument to the task's handlers. The instance of this class
3069 parameter automatically becomes a member of the TaskAdapter template, and is
3070 accessible through the TaskAdapter::task() method. The constructor
3071 of \c TimerTaskAdapter initially configures the QTimer object and connects
3072 to the QTimer::timeout() signal. When the signal is triggered, \c TimerTaskAdapter
3073 emits the TaskInterface::done(DoneResult::Success) signal to inform the task tree that
3074 the task finished successfully. If it emits TaskInterface::done(DoneResult::Error),
3075 the task finished with an error.
3076 The TaskAdapter::start() method starts the timer.
3077
3078 To make QTimer accessible inside TaskTree under the \c TimerTask name,
3079 define \c TimerTask to be an alias to the CustomTask<\c TimerTaskAdapter>.
3080 \c TimerTask becomes a new custom task type, using \c TimerTaskAdapter.
3081
3082 The new task type is now registered, and you can use it in TaskTree:
3083
3084 \code
3085 const auto onSetup = [](QTimer &task) { task.setInterval(2000); };
3086 const auto onDone = [] { qDebug() << "timer triggered"; };
3087 const Group root {
3088 TimerTask(onSetup, onDone)
3089 };
3090 \endcode
3091
3092 When a task tree containing the root from the above example is started, it
3093 prints a debug message within two seconds and then finishes successfully.
3094
3095 \note The class implementing the running task should have a default constructor,
3096 and objects of this class should be freely destructible. It should be allowed
3097 to destroy a running object, preferably without waiting for the running task
3098 to finish (that is, safe non-blocking destructor of a running task).
3099 To achieve a non-blocking destruction of a task that has a blocking destructor,
3100 consider using the optional \c Deleter template parameter of the TaskAdapter.
3101*/
3102
3103/*!
3104 Constructs an empty task tree. Use setRecipe() to pass a declarative description
3105 on how the task tree should execute the tasks and how it should handle the finished tasks.
3106
3107 Starting an empty task tree is no-op and the relevant warning message is issued.
3108
3109 \sa setRecipe(), start()
3110*/
3111TaskTree::TaskTree()
3112 : d(new TaskTreePrivate(this))
3113{}
3114
3115/*!
3116 \overload
3117
3118 Constructs a task tree with a given \a recipe. After the task tree is started,
3119 it executes the tasks contained inside the \a recipe and
3120 handles finished tasks according to the passed description.
3121
3122 \sa setRecipe(), start()
3123*/
3124TaskTree::TaskTree(const Group &recipe) : TaskTree()
3125{
3126 setRecipe(recipe);
3127}
3128
3129/*!
3130 Destroys the task tree.
3131
3132 When the task tree is running while being destructed, it cancels all the running tasks
3133 immediately. In this case, no handlers are called, not even the groups' and
3134 tasks' done handlers or onStorageDone() handlers. The task tree also doesn't emit any
3135 signals from the destructor, not even done() or progressValueChanged() signals.
3136 This behavior may always be relied on.
3137 It is completely safe to destruct the running task tree.
3138
3139 It's a usual pattern to destruct the running task tree.
3140 It's guaranteed that the destruction will run quickly, without having to wait for
3141 the currently running tasks to finish, provided that the used tasks implement
3142 their destructors in a non-blocking way.
3143
3144 \note Do not call the destructor directly from any of the running task's handlers
3145 or task tree's signals. In these cases, use \l deleteLater() instead.
3146
3147 \sa cancel()
3148*/
3150{
3151 QT_ASSERT(!d->m_guard.isLocked(), qWarning("Deleting TaskTree instance directly from "
3152 "one of its handlers will lead to a crash!"));
3153 // TODO: delete storages explicitly here?
3154 delete d;
3155}
3156
3157/*!
3158 Sets a given \a recipe for the task tree. After the task tree is started,
3159 it executes the tasks contained inside the \a recipe and
3160 handles finished tasks according to the passed description.
3161
3162 \note When called for a running task tree, the call is ignored.
3163
3164 \sa TaskTree(const Tasking::Group &recipe), start()
3165*/
3166void TaskTree::setRecipe(const Group &recipe)
3167{
3168 QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
3169 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The setRecipe() is called from one of the"
3170 "TaskTree handlers, ignoring..."); return);
3171 // TODO: Should we clear the m_storageHandlers, too?
3172 d->m_storages.clear();
3173 d->m_root.emplace(d, recipe);
3174}
3175
3176/*!
3177 Starts the task tree.
3178
3179 Use setRecipe() or the constructor to set the declarative description according to which
3180 the task tree will execute the contained tasks and handle finished tasks.
3181
3182 When the task tree is empty, that is, constructed with a default constructor,
3183 a call to \c start() is no-op and the relevant warning message is issued.
3184
3185 Otherwise, when the task tree is already running, a call to \e start() is ignored and the
3186 relevant warning message is issued.
3187
3188 Otherwise, the task tree is started.
3189
3190 The started task tree may finish synchronously,
3191 for example when the main group's start handler returns SetupResult::StopWithError.
3192 For this reason, the connection to the done signal should be established before calling
3193 \c start(). Use isRunning() in order to detect whether the task tree is still running
3194 after a call to \c start().
3195
3196 The task tree implementation relies on the running event loop.
3197 Make sure you have a QEventLoop or QCoreApplication or one of its
3198 subclasses running (or about to be run) when calling this method.
3199
3200 \sa TaskTree(const Tasking::Group &), setRecipe(), isRunning(), cancel()
3201*/
3203{
3204 QT_ASSERT(!isRunning(), qWarning("The TaskTree is already running, ignoring..."); return);
3205 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The start() is called from one of the"
3206 "TaskTree handlers, ignoring..."); return);
3207 d->start();
3208}
3209
3210/*!
3211 \fn void TaskTree::started()
3212
3213 This signal is emitted when the task tree is started. The emission of this signal is
3214 followed synchronously by the progressValueChanged() signal with an initial \c 0 value.
3215
3216 \sa start(), done()
3217*/
3218
3219/*!
3220 \fn void TaskTree::done(DoneWith result)
3221
3222 This signal is emitted when the task tree finished, passing the final \a result
3223 of the execution. The task tree neither calls any handler,
3224 nor emits any signal anymore after this signal was emitted.
3225
3226 \note Do not delete the task tree directly from this signal's handler.
3227 Use deleteLater() instead.
3228
3229 \sa started()
3230*/
3231
3232/*!
3233 Cancels the execution of the running task tree.
3234
3235 Cancels all the running tasks immediately.
3236 All running tasks finish with an error, invoking their error handlers.
3237 All running groups dispatch their handlers according to their workflow policies,
3238 invoking their done handlers. The storages' onStorageDone() handlers are invoked, too.
3239 The progressValueChanged() signals are also being sent.
3240 This behavior may always be relied on.
3241
3242 The \c cancel() function is executed synchronously, so that after a call to \c cancel()
3243 all running tasks are finished and the tree is already canceled.
3244 It's guaranteed that \c cancel() will run quickly, without any blocking wait for
3245 the currently running tasks to finish, provided the used tasks implement their destructors
3246 in a non-blocking way.
3247
3248 When the task tree is empty, that is, constructed with a default constructor,
3249 a call to \c cancel() is no-op and the relevant warning message is issued.
3250
3251 Otherwise, when the task tree wasn't started, a call to \c cancel() is ignored.
3252
3253 \note Do not call this function directly from any of the running task's handlers
3254 or task tree's signals.
3255
3256 \sa ~TaskTree()
3257*/
3259{
3260 QT_ASSERT(!d->m_guard.isLocked(), qWarning("The cancel() is called from one of the"
3261 "TaskTree handlers, ignoring..."); return);
3262 d->stop();
3263}
3264
3265/*!
3266 Returns \c true if the task tree is currently running; otherwise returns \c false.
3267
3268 \sa start(), cancel()
3269*/
3270bool TaskTree::isRunning() const
3271{
3272 return bool(d->m_runtimeRoot);
3273}
3274
3275/*!
3276 Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree.
3277
3278 Returns DoneWith::Success if the task tree finished successfully;
3279 otherwise returns DoneWith::Error.
3280
3281 \note Avoid using this method from the main thread. Use asynchronous start() instead.
3282 This method is to be used in non-main threads or in auto tests.
3283
3284 \sa start()
3285*/
3287{
3288 QPromise<void> dummy;
3289 dummy.start();
3290 return runBlocking(dummy.future());
3291}
3292
3293/*!
3294 \overload runBlocking()
3296 The passed \a future is used for listening to the cancel event.
3297 When the task tree is canceled, this method cancels the passed \a future.
3298*/
3299DoneWith TaskTree::runBlocking(const QFuture<void> &future)
3300{
3301 if (future.isCanceled())
3302 return DoneWith::Cancel;
3303
3304 DoneWith doneWith = DoneWith::Cancel;
3305 QEventLoop loop;
3306 connect(this, &TaskTree::done, &loop, [&loop, &doneWith](DoneWith result) {
3307 doneWith = result;
3308 // Otherwise, the tasks from inside the running tree that were deleteLater()
3309 // will be leaked. Refer to the QObject::deleteLater() docs.
3310 QMetaObject::invokeMethod(&loop, [&loop] { loop.quit(); }, Qt::QueuedConnection);
3311 });
3312 QFutureWatcher<void> watcher;
3313 connect(&watcher, &QFutureWatcherBase::canceled, this, &TaskTree::cancel);
3314 watcher.setFuture(future);
3315
3316 QTimer::singleShot(0, this, &TaskTree::start);
3317
3318 loop.exec(QEventLoop::ExcludeUserInputEvents);
3319 if (doneWith == DoneWith::Cancel) {
3320 auto nonConstFuture = future;
3321 nonConstFuture.cancel();
3322 }
3323 return doneWith;
3324}
3325
3326/*!
3327 Constructs a temporary task tree using the passed \a recipe and runs it blocking.
3328
3329 Returns DoneWith::Success if the task tree finished successfully;
3330 otherwise returns DoneWith::Error.
3331
3332 \note Avoid using this method from the main thread. Use asynchronous start() instead.
3333 This method is to be used in non-main threads or in auto tests.
3334
3335 \sa start()
3336*/
3338{
3339 QPromise<void> dummy;
3340 dummy.start();
3341 return TaskTree::runBlocking(recipe, dummy.future());
3342}
3343
3344/*!
3345 \overload runBlocking(const Group &recipe)
3347 The passed \a future is used for listening to the cancel event.
3348 When the task tree is canceled, this method cancels the passed \a future.
3349*/
3350DoneWith TaskTree::runBlocking(const Group &recipe, const QFuture<void> &future)
3351{
3352 TaskTree taskTree(recipe);
3353 return taskTree.runBlocking(future);
3354}
3355
3356/*!
3357 Returns the current real count of asynchronous chains of invocations.
3358
3359 The returned value indicates how many times the control returns to the caller's
3360 event loop while the task tree is running. Initially, this value is 0.
3361 If the execution of the task tree finishes fully synchronously, this value remains 0.
3362 If the task tree contains any asynchronous tasks that are successfully started during
3363 a call to start(), this value is bumped to 1 just before the call to start() finishes.
3364 Later, when any asynchronous task finishes and any possible continuations are started,
3365 this value is bumped again. The bumping continues until the task tree finishes.
3366 When the task tree emits the done() signal, the bumping stops.
3367 The asyncCountChanged() signal is emitted on every bump of this value.
3368
3369 \sa asyncCountChanged()
3370*/
3372{
3373 return d->m_asyncCount;
3374}
3375
3376/*!
3377 \fn void TaskTree::asyncCountChanged(int count)
3378
3379 This signal is emitted when the running task tree is about to return control to the caller's
3380 event loop. When the task tree is started, this signal is emitted with \a count value of 0,
3381 and emitted later on every asyncCount() value bump with an updated \a count value.
3382 Every signal sent (except the initial one with the value of 0) guarantees that the task tree
3383 is still running asynchronously after the emission.
3384
3385 \sa asyncCount()
3386*/
3387
3388/*!
3389 Returns the number of asynchronous tasks contained in the stored recipe.
3390
3391 \note The returned number doesn't include \l {Tasking::Sync} {Sync} tasks.
3392 \note Any task or group that was set up using withTimeout() increases the total number of
3393 tasks by \c 1.
3394
3395 \sa setRecipe(), progressMaximum()
3396*/
3398{
3399 return d->m_root ? d->m_root->taskCount() : 0;
3400}
3401
3402/*!
3403 \fn void TaskTree::progressValueChanged(int value)
3404
3405 This signal is emitted when the running task tree finished, canceled, or skipped some tasks.
3406 The \a value gives the current total number of finished, canceled or skipped tasks.
3407 When the task tree is started, and after the started() signal was emitted,
3408 this signal is emitted with an initial \a value of \c 0.
3409 When the task tree is about to finish, and before the done() signal is emitted,
3410 this signal is emitted with the final \a value of progressMaximum().
3411
3412 \sa progressValue(), progressMaximum()
3413*/
3414
3415/*!
3416 \fn int TaskTree::progressMaximum() const
3417
3418 Returns the maximum progressValue().
3419
3420 \note Currently, it's the same as taskCount(). This might change in the future.
3421
3422 \sa progressValue()
3423*/
3424
3425/*!
3426 Returns the current progress value, which is between the \c 0 and progressMaximum().
3427
3428 The returned number indicates how many tasks have been already finished, canceled, or skipped
3429 while the task tree is running.
3430 When the task tree is started, this number is set to \c 0.
3431 When the task tree is finished, this number always equals progressMaximum().
3432
3433 \sa progressMaximum(), progressValueChanged()
3434*/
3436{
3437 return d->m_progressValue;
3438}
3439
3440/*!
3441 \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageSetup(const Storage<StorageStruct> &storage, Handler &&handler)
3442
3443 Installs a storage setup \a handler for the \a storage to pass the initial data
3444 dynamically to the running task tree.
3445
3446 The \c StorageHandler takes a \e reference to the \c StorageStruct instance:
3447
3448 \code
3449 static void save(const QString &fileName, const QByteArray &array) { ... }
3450
3451 Storage<QByteArray> storage;
3452
3453 const auto onSaverSetup = [storage](ConcurrentCall<QByteArray> &concurrent) {
3454 concurrent.setConcurrentCallData(&save, "foo.txt", *storage);
3455 };
3456
3457 const Group root {
3458 storage,
3459 ConcurrentCallTask(onSaverSetup)
3460 };
3461
3462 TaskTree taskTree(root);
3463 auto initStorage = [](QByteArray &storage){
3464 storage = "initial content";
3465 };
3466 taskTree.onStorageSetup(storage, initStorage);
3467 taskTree.start();
3468 \endcode
3469
3470 When the running task tree enters a Group where the \a storage is placed in,
3471 it creates a \c StorageStruct instance, ready to be used inside this group.
3472 Just after the \c StorageStruct instance is created, and before any handler of this group
3473 is called, the task tree invokes the passed \a handler. This enables setting up
3474 initial content for the given storage dynamically. Later, when any group's handler is invoked,
3475 the task tree activates the created and initialized storage, so that it's available inside
3476 any group's handler.
3477
3478 \sa onStorageDone()
3479*/
3480
3481/*!
3482 \fn template <typename StorageStruct, typename Handler> void TaskTree::onStorageDone(const Storage<StorageStruct> &storage, Handler &&handler)
3483
3484 Installs a storage done \a handler for the \a storage to retrieve the final data
3485 dynamically from the running task tree.
3486
3487 The \c StorageHandler takes a \c const \e reference to the \c StorageStruct instance:
3488
3489 \code
3490 static QByteArray load(const QString &fileName) { ... }
3491
3492 Storage<QByteArray> storage;
3493
3494 const auto onLoaderSetup = [](ConcurrentCall<QByteArray> &concurrent) {
3495 concurrent.setConcurrentCallData(&load, "foo.txt");
3496 };
3497 const auto onLoaderDone = [storage](const ConcurrentCall<QByteArray> &concurrent) {
3498 *storage = concurrent.result();
3499 };
3500
3501 const Group root {
3502 storage,
3503 ConcurrentCallTask(onLoaderSetup, onLoaderDone, CallDoneIf::Success)
3504 };
3505
3506 TaskTree taskTree(root);
3507 auto collectStorage = [](const QByteArray &storage){
3508 qDebug() << "final content" << storage;
3509 };
3510 taskTree.onStorageDone(storage, collectStorage);
3511 taskTree.start();
3512 \endcode
3513
3514 When the running task tree is about to leave a Group where the \a storage is placed in,
3515 it destructs a \c StorageStruct instance.
3516 Just before the \c StorageStruct instance is destructed, and after all possible handlers from
3517 this group were called, the task tree invokes the passed \a handler. This enables reading
3518 the final content of the given storage dynamically and processing it further outside of
3519 the task tree.
3520
3521 This handler is called also when the running tree is canceled. However, it's not called
3522 when the running tree is destructed.
3523
3524 \sa onStorageSetup()
3525*/
3526
3527void TaskTree::setupStorageHandler(const StorageBase &storage,
3528 const StorageBase::StorageHandler &setupHandler,
3529 const StorageBase::StorageHandler &doneHandler)
3530{
3531 auto it = d->m_storageHandlers.find(storage);
3532 if (it == d->m_storageHandlers.end()) {
3533 d->m_storageHandlers.insert(storage, {setupHandler, doneHandler});
3534 return;
3535 }
3536 if (setupHandler) {
3537 QT_ASSERT(!it->m_setupHandler,
3538 qWarning("The storage has its setup handler defined, overriding..."));
3539 it->m_setupHandler = setupHandler;
3540 }
3541 if (doneHandler) {
3542 QT_ASSERT(!it->m_doneHandler,
3543 qWarning("The storage has its done handler defined, overriding..."));
3544 it->m_doneHandler = doneHandler;
3545 }
3546}
3547
3549{
3550 connect(task(), &TaskTree::done, this,
3551 [this](DoneWith result) { emit done(toDoneResult(result)); });
3552}
3553
3555{
3556 task()->start();
3557}
3558
3560
3567
3569{
3571
3572 TimerThreadData() = default; // defult constructor is required for initializing with {} since C++20 by Mingw 11.20
3576};
3577
3578// Please note the thread_local keyword below guarantees a separate instance per thread.
3579static thread_local TimerThreadData s_threadTimerData = {};
3580
3581static void removeTimerId(int timerId)
3582{
3583 const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(timerId);
3584 QT_ASSERT(it != s_threadTimerData.m_timerIdToTimerData.cend(),
3585 qWarning("Removing active timerId failed."); return);
3586
3587 const system_clock::time_point deadline = it->m_deadline;
3588 s_threadTimerData.m_timerIdToTimerData.erase(it);
3589
3590 QList<int> &ids = s_threadTimerData.m_deadlineToTimerId[deadline];
3591 const int removedCount = ids.removeAll(timerId);
3592 QT_ASSERT(removedCount == 1, qWarning("Removing active timerId failed."); return);
3593 if (ids.isEmpty())
3594 s_threadTimerData.m_deadlineToTimerId.remove(deadline);
3595}
3596
3597static void handleTimeout(int timerId)
3598{
3599 const auto itData = s_threadTimerData.m_timerIdToTimerData.constFind(timerId);
3600 if (itData == s_threadTimerData.m_timerIdToTimerData.cend())
3601 return; // The timer was already activated.
3602
3603 const auto deadline = itData->m_deadline;
3604 while (true) {
3605 auto itMap = s_threadTimerData.m_deadlineToTimerId.begin();
3606 if (itMap == s_threadTimerData.m_deadlineToTimerId.end())
3607 return;
3608
3609 if (itMap.key() > deadline)
3610 return;
3611
3612 std::optional<TimerData> timerData;
3613 QList<int> &idList = *itMap;
3614 if (!idList.isEmpty()) {
3615 const int first = idList.first();
3616 idList.removeFirst();
3617
3618 const auto it = s_threadTimerData.m_timerIdToTimerData.constFind(first);
3619 if (it != s_threadTimerData.m_timerIdToTimerData.cend()) {
3620 timerData = it.value();
3621 s_threadTimerData.m_timerIdToTimerData.erase(it);
3622 } else {
3623 QT_CHECK(false);
3624 }
3625 } else {
3626 QT_CHECK(false);
3627 }
3628
3629 if (idList.isEmpty())
3630 s_threadTimerData.m_deadlineToTimerId.erase(itMap);
3631 if (timerData && timerData->m_context)
3632 timerData->m_callback();
3633 }
3634}
3635
3636static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback)
3637{
3638 const int timerId = ++s_threadTimerData.m_timerIdCounter;
3639 const system_clock::time_point deadline = system_clock::now() + timeout;
3640 QTimer::singleShot(timeout, context, [timerId] { handleTimeout(timerId); });
3641 s_threadTimerData.m_timerIdToTimerData.emplace(timerId, TimerData{deadline, context, callback});
3642 s_threadTimerData.m_deadlineToTimerId[deadline].append(timerId);
3643 return timerId;
3644}
3645
3647{
3648 *task() = milliseconds::zero();
3649}
3650
3652{
3653 if (m_timerId)
3654 removeTimerId(*m_timerId);
3655}
3656
3658{
3659 m_timerId = scheduleTimeout(*task(), this, [this] {
3660 m_timerId.reset();
3661 emit done(DoneResult::Success);
3662 });
3663}
3664
3665ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, DoneResult result)
3666{
3667 return TimeoutTask([timeout](std::chrono::milliseconds &t) { t = timeout; }, result);
3668}
3669
3670/*!
3671 \typealias Tasking::TaskTreeTask
3672
3673 Type alias for the CustomTask, to be used inside recipes, associated with the TaskTree task.
3674*/
3675
3676/*!
3677 \typealias Tasking::TimeoutTask
3678
3679 Type alias for the CustomTask, to be used inside recipes, associated with the
3680 \c std::chrono::milliseconds type. \c std::chrono::milliseconds is used to set up the
3681 timeout duration. The default timeout is \c std::chrono::milliseconds::zero(), that is,
3682 the TimeoutTask finishes as soon as the control returns to the running event loop.
3683
3684 Example usage:
3685
3686 \code
3687 using namespace std::chrono;
3688 using namespace std::chrono_literals;
3689
3690 const auto onSetup = [](milliseconds &timeout) { timeout = 1000ms; }
3691 const auto onDone = [] { qDebug() << "Timed out."; }
3692
3693 const Group root {
3694 Timeout(onSetup, onDone)
3695 };
3696 \endcode
3697*/
3698
3699} // namespace Tasking
3700
3701QT_END_NAMESPACE
#define QT_STRING(cond)
Definition barrier.cpp:12
#define QT_ASSERT(cond, action)
Definition barrier.cpp:13
void setLimit(int value)
Definition barrier.cpp:15
const WorkflowPolicy m_workflowPolicy
const std::optional< Loop > m_loop
const GroupItem::GroupHandler m_groupHandler
std::vector< TaskNode > m_children
TaskTreePrivate *const m_taskTreePrivate
const QList< StorageBase > m_storageList
ContainerNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
\inheaderfile solutions/tasking/tasktree.h \inmodule TaskingSolution
Definition tasktree.h:317
Group withLog(const QString &logName) const
Attaches a custom debug printout to a copy of this ExecutableItem, issued on task startup and after t...
Group withTimeout(std::chrono::milliseconds timeout, const std::function< void()> &handler={}) const
Attaches TimeoutTask to a copy of this ExecutableItem, elapsing after timeout in milliseconds,...
ExecutionContextActivator(RuntimeContainer *container)
ExecutionContextActivator(RuntimeIteration *iteration)
\typealias Tasking::GroupItems
Definition tasktree.h:226
void addChildren(const GroupItems &children)
\inheaderfile solutions/tasking/tasktree.h \inmodule TaskingSolution
Definition tasktree.h:348
bool isLocked() const
Definition tasktree.cpp:40
std::map< QThread *, LoopThreadData > m_threadDataMap
const std::optional< int > m_loopCount
const Loop::ValueGetter m_valueGetter
LoopThreadData & threadData()
const Loop::Condition m_condition
void pushIteration(int index)
Loop(int count, const ValueGetter &valueGetter={})
Loop(const Condition &condition)
int iteration() const
const void * valuePtr() const
bool updateSuccessBit(bool success)
const QList< StoragePtr > m_storages
const ContainerNode & m_containerNode
RuntimeIteration * parentIteration() const
int progressiveLoopCount() const
std::vector< std::unique_ptr< RuntimeIteration > > m_iterations
std::optional< Loop > loop() const
RuntimeContainer * m_container
std::vector< std::shared_ptr< RuntimeTask > > m_children
void removeChild(RuntimeTask *node)
RuntimeIteration * m_parentIteration
SetupResult m_setupResult
std::unique_ptr< TaskInterface > m_task
std::optional< RuntimeContainer > m_container
const TaskNode & m_taskNode
friend class Storage
Definition tasktree.h:188
std::map< QThread *, StorageThreadData > m_threadDataMap
const StorageBase::StorageDestructor m_destructor
const StorageBase::StorageConstructor m_constructor
StorageThreadData & threadData()
void pushStorage(StoragePtr storagePtr)
StoragePtr activeStorage() const
const GroupItem::TaskHandler m_taskHandler
int taskCount() const
ContainerNode m_container
TaskNode(TaskTreePrivate *taskTreePrivate, const GroupItem &task)
bool isTask() const
void stopTask(RuntimeTask *node)
static int effectiveLoopCount(const std::optional< Loop > &loop)
bool invokeDoneHandler(RuntimeContainer *container, DoneWith doneWith)
QSet< StorageBase > m_storages
std::optional< TaskNode > m_root
bool invokeLoopHandler(RuntimeContainer *container)
void emitDone(DoneWith result)
void startTask(const std::shared_ptr< RuntimeTask > &node)
void callSetupHandler(const StorageBase &storage, StoragePtr storagePtr)
StorageBase::StorageHandler StorageHandler::* HandlerPtr
QHash< StorageBase, StorageHandler > m_storageHandlers
bool invokeTaskDoneHandler(RuntimeTask *node, DoneWith doneWith)
void continueContainer(RuntimeContainer *container)
void stopContainer(RuntimeContainer *container)
std::shared_ptr< RuntimeTask > m_runtimeRoot
void advanceProgress(int byValue)
void callDoneHandler(const StorageBase &storage, StoragePtr storagePtr)
void childDone(RuntimeIteration *iteration, bool success)
ReturnType invokeHandler(Container *container, Handler &&handler, Args &&...args)
void callStorageHandler(const StorageBase &storage, StoragePtr storagePtr, HandlerPtr ptr)
void startChildren(RuntimeContainer *container)
void start() final
This method is called by the running TaskTree for starting the Task instance.
void done(DoneWith result)
This signal is emitted when the task tree finished, passing the final result of the execution.
DoneWith runBlocking(const QFuture< void > &future)
TaskTree()
Constructs an empty task tree.
int taskCount() const
Returns the number of asynchronous tasks contained in the stored recipe.
void cancel()
Cancels the execution of the running task tree.
void start()
Starts the task tree.
~TaskTree()
Destroys the task tree.
bool isRunning() const
Returns true if the task tree is currently running; otherwise returns false.
void setRecipe(const Group &recipe)
Sets a given recipe for the task tree.
TaskTree(const Group &recipe)
This is an overloaded member function, provided for convenience. It differs from the above function o...
int progressValue() const
Returns the current progress value, which is between the 0 and progressMaximum().
DoneWith runBlocking()
Executes a local event loop with QEventLoop::ExcludeUserInputEvents and starts the task tree.
static DoneWith runBlocking(const Group &recipe)
Constructs a temporary task tree using the passed recipe and runs it blocking.
int asyncCount() const
Returns the current real count of asynchronous chains of invocations.
void start() final
This method is called by the running TaskTree for starting the Task instance.
TASKING_EXPORT friend Group operator>>(const When &whenItem, const Do &doItem)
\inmodule TaskingSolution
const ExecutableItem successItem
SetupResult
Definition tasktree.h:65
WorkflowPolicy
Definition tasktree.h:53
const GroupItem sequential
static DoneResult toDoneResult(DoneWith doneWith)
GroupItem parallelLimit(int limit)
Constructs a group's element describing the \l{Execution Mode}{execution mode}.
Group operator||(const ExecutableItem &first, const ExecutableItem &second)
static int scheduleTimeout(milliseconds timeout, QObject *context, const TimeoutCallback &callback)
static bool shouldCall(CallDoneIf callDoneIf, DoneWith result)
const GroupItem continueOnSuccess
static TaskTree * activeTaskTree()
const GroupItem stopOnError
const GroupItem finishAllAndError
const ExecutableItem errorItem
static bool isProgressive(RuntimeContainer *container)
const GroupItem finishAllAndSuccess
static std::vector< TaskNode > createChildren(TaskTreePrivate *taskTreePrivate, const GroupItems &children)
static thread_local QList< TaskTree * > s_activeTaskTrees
Group operator||(const ExecutableItem &item, DoneResult result)
DoneResult toDoneResult(bool success)
const GroupItem parallelIdealThreadCountLimit
ExecutableItem timeoutTask(const std::chrono::milliseconds &timeout, DoneResult result)
static SetupResult toSetupResult(bool success)
const GroupItem stopOnSuccessOrError
static QString currentTime()
const GroupItem nullItem
Group operator&&(const ExecutableItem &item, DoneResult result)
const GroupItem parallel
Group operator&&(const ExecutableItem &first, const ExecutableItem &second)
Group operator>>(const For &forItem, const Do &doItem)
static QString logHeader(const QString &logName)
Group operator!(const ExecutableItem &item)
const GroupItem continueOnError
void * StoragePtr
GroupItem workflowPolicy(WorkflowPolicy policy)
Constructs a group's \l {Workflow Policy} {workflow policy} element for a given policy.
const GroupItem stopOnSuccess
ThenItem operator>>(const If &ifItem, const Then &thenItem)
static thread_local TimerThreadData s_threadTimerData
static void handleTimeout(int timerId)
static DoneWith toDoneWith(DoneResult result)
static bool initialSuccessBit(WorkflowPolicy workflowPolicy)
static constexpr QLatin1StringView s_activeStorageWarning
static void removeTimerId(int timerId)
StorageBase::StorageHandler m_setupHandler
StorageBase::StorageHandler m_doneHandler
QPointer< QObject > m_context
system_clock::time_point m_deadline
TimeoutCallback m_callback
QHash< int, TimerData > m_timerIdToTimerData
#define QT_CHECK(cond)
Definition tasktree.cpp:32