Storage Class

template <typename StorageStruct> class Tasking::Storage

A class template for custom data exchange in the running task tree. More...

Header: #include <solutions/tasking/tasktree.h>
Inherits: Tasking::StorageBase

Note: All functions in this class are reentrant.

Public Functions

Storage()
StorageStruct *activeStorage() const
StorageStruct &operator*() const
StorageStruct *operator->() const

Detailed Description

The Storage class template is responsible for dynamically creating and destructing objects of the custom StorageStruct type. The creation and destruction are managed by the running task tree. If a Storage object is placed inside a Group element, the running task tree creates the StorageStruct object when the group is started and before the group's setup handler is called. Later, whenever any handler inside this group is called, the task tree activates the previously created instance of the StorageStruct object. This includes all tasks' and groups' setup and done handlers inside the group where the Storage object was placed, also within the nested groups. When a copy of the Storage object is passed to the handler via the lambda capture, the handler may access the instance activated by the running task tree via the operator->(), operator*(), or activeStorage() method. If two handlers capture the same Storage object, one of them may store a custom data there, and the other may read it afterwards. When the group is finished, the previously created instance of the StorageStruct object is destroyed after the group's done handler is called.

An example of data exchange between tasks:

const Storage<QString> storage;

const auto onFirstDone = [storage](const Task &task) {
    // Assings QString, taken from the first task result, to the active QString instance
    // of the Storage object.
    *storage = task.getResultAsString();
};

const auto onSecondSetup = [storage](Task &task) {
    // Reads QString from the active QString instance of the Storage object and use it to
    // configure the second task before start.
    task.configureWithString(*storage);
};

const Group root {
    // The running task tree creates QString instance when root in entered
    storage,
    // The done handler of the first task stores the QString in the storage
    TaskItem(..., onFirstDone),
    // The setup handler of the second task reads the QString from the storage
    TaskItem(onSecondSetup, ...)
};

Since the root group executes its tasks sequentially, the onFirstDone handler is always called before the onSecondSetup handler. This means that the QString data, read from the storage inside the onSecondSetup handler's body, has already been set by the onFirstDone handler. You can always rely on it in sequential execution mode.

The Storage internals are shared between all of its copies. That is why the copies of the Storage object inside the handlers' lambda captures still refer to the same Storage instance. You may place multiple Storage objects inside one Group element, provided that they do not include copies of the same Storage object. Otherwise, an assert is triggered at runtime that includes an error message. However, you can place copies of the same Storage object in different Group elements of the same recipe. In this case, the running task tree will create multiple instances of the StorageStruct objects (one for each copy) and storage shadowing will take place. Storage shadowing works in a similar way to C++ variable shadowing inside the nested blocks of code:

Storage<QString> storage;

const Group root {
    storage,                             // Top copy, 1st instance of StorageStruct
    onGroupSetup([storage] { ... }),     // Top copy is active
    Group {
        storage,                         // Nested copy, 2nd instance of StorageStruct,
                                         // shadows Top copy
        onGroupSetup([storage] { ... }), // Nested copy is active
    },
    Group {
        onGroupSetup([storage] { ... }), // Top copy is active
    }
};

The Storage objects may also be used for passing the initial data to the executed task tree, and for reading the final data out of the task tree before it finishes. To do this, use onStorageSetup() or onStorageDone(), respectively.

Note: If you use an unreachable Storage object inside the handler, because you forgot to place the storage in the recipe, or placed it, but not in any handler's ancestor group, you may expect a crash, preceded by the following message: The referenced storage is not reachable in the running tree. A nullptr will be returned which might lead to a crash in the calling code. It is possible that no storage was added to the tree, or the storage is not reachable from where it is referenced.

Member Function Documentation

Storage::Storage()

Creates a storage for the given StorageStruct type.

Note: All copies of this object are considered to be the same Storage instance.

StorageStruct *Storage::activeStorage() const

Returns a pointer to the active StorageStruct object, created by the running task tree. Use this function only from inside the handler body of any GroupItem element placed in the recipe, otherwise you may expect a crash. Make sure that Storage is placed in any group ancestor of the handler's group item.

Note: The returned pointer is valid as long as the group that created this instance is still running.

See also operator->() and operator*().

[noexcept] StorageStruct &Storage::operator*() const

Returns a reference to the active StorageStruct object, created by the running task tree. Use this function only from inside the handler body of any GroupItem element placed in the recipe, otherwise you may expect a crash. Make sure that Storage is placed in any group ancestor of the handler's group item.

Note: The returned reference is valid as long as the group that created this instance is still running.

See also activeStorage() and operator->().

[noexcept] StorageStruct *Storage::operator->() const

Returns a pointer to the active StorageStruct object, created by the running task tree. Use this function only from inside the handler body of any GroupItem element placed in the recipe, otherwise you may expect a crash. Make sure that Storage is placed in any group ancestor of the handler's group item.

Note: The returned pointer is valid as long as the group that created this instance is still running.

See also activeStorage() and operator*().

© 2024 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.