Reference documentation for deal.II version GIT relicensing-1062-gc06da148b8 2024-07-15 19:20:02+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
Public Member Functions | Private Member Functions | Private Attributes | List of all members
Threads::TaskResult< T > Class Template Reference

#include <deal.II/base/task_result.h>

Public Member Functions

 TaskResult ()
 
 TaskResult (const Task< T > &task)
 
 TaskResult (const TaskResult< T > &)=delete
 
 TaskResult (TaskResult< T > &&other) noexcept
 
 ~TaskResult ()
 
void operator= (const Task< T > &t)
 
void clear ()
 
void join ()
 
const T & value () const
 

Private Member Functions

void wait_and_move_result () const
 

Private Attributes

std::atomic< boolresult_is_available
 
std::optional< Task< T > > task
 
std::optional< T > task_result
 
std::mutex mutex
 

Detailed Description

template<typename T>
class Threads::TaskResult< T >

A class that represents the outcome of a Threads::Task. The class is used as a member variable or local variable when something is computed on a separate task in the background. For example, a class's constructor may want to set up some expensive data structures that are not typically used right away in the next line of the function that created the object, but will be useful later; in such a case, it may defer computation of that data to a separate task that will be scheduled whenever a CPU core is available, and the result of that deferred computation will become available through a variable of the current type.

In some regard, class TaskResult is similar to class Lazy in that it defers construction of an object to a later point while already giving it a home. The difference is simply that TaskResult has the creation job already scheduled whereas Lazy defers creation to the first use. Lazy is therefore a better choice whenever the object referenced may or may not ever be used, say for some obscure functionality of a class that is rarely required. On the other hand, TaskResult is used for member variables that are definitely needed, but perhaps not right away.

This class has an interface that is quite similar to the Threads::Task class itself, and object of which is provided to the constructor. The key difference is that the Task class describes the task that computes a result, whereas the current TaskResult class describes the result that is being computed by the task. The main practical differences between these perspectives are what happens when you want to copy an object: Copying a Task object results in two objects that are both referencing the same task, with the same returned object when the task has finished. On the other hand, copying a TaskResult object after the computing task has finished results in two copies of the returned object.

This class can also be compared with std::future. That class also represents the result of a pending operation, but it lacks the specifics of what kind of operation that is (in particular, it does not know whether a task, a thread, or a std::packaged_task will eventually produce the value). As a consequence, it can wait for the result to become available, but it lacks the knowledge to detect certain common programming mistakes such as those described in the documentation of the destructor of this class, or of this class's operator=(). Another significant difference is that one can only call std::future::get() once, whereas one can call Threads::Task::return_value() as many times as desired. It is, thus, comparable to the std::shared_future class. However, std::shared_future can not be used for types that can not be copied – a particular restriction for std::unique_ptr, for example.

Definition at line 87 of file task_result.h.

Constructor & Destructor Documentation

◆ TaskResult() [1/4]

template<typename T >
Threads::TaskResult< T >::TaskResult ( )
inline

Default constructor. An object of this kind is not (presently) associated with a task, and so cannot be asked for that task's result.

Definition at line 95 of file task_result.h.

◆ TaskResult() [2/4]

template<typename T >
Threads::TaskResult< T >::TaskResult ( const Task< T > &  task)
inline

A constructor that takes a Task object and initializes the current object to be the result of that task.

Definition at line 103 of file task_result.h.

◆ TaskResult() [3/4]

template<typename T >
Threads::TaskResult< T >::TaskResult ( const TaskResult< T > &  )
delete

Copy constructor. Because the result of a currently still running task is a unique object, TaskResult objects cannot be copied.

◆ TaskResult() [4/4]

template<typename T >
Threads::TaskResult< T >::TaskResult ( TaskResult< T > &&  other)
inlinenoexcept

Move constructor. Following this call, the newly created object represents the task's result, whereas the old object no longer represents anything and is left as if default-constructed.

Definition at line 312 of file task_result.h.

◆ ~TaskResult()

template<typename T >
Threads::TaskResult< T >::~TaskResult ( )
inline

Destructor. If the current object was associated with a task, then the destructor will throw an error if that task is still running. This is because typically, the task will still be working on data that is already gone away, or will shortly go away, and that means that that task will likely encounter unpredictable outcomes. As an example, consider the following code:

class Complicated {
public:
Complicated () {
...initialize members...;
hash_value = Threads::new_task(
[&]() { compute has value for the current object; });
}
~Complicated() { ... }
private:
...complicated data members...
...more data members...
};
Task< RT > new_task(const std::function< RT()> &function)

Here, the constructor ~Complicated() destroys the object's member variables, and then calls the destructor of hash_value. If at the time we get to the latter destructor the background task to compute a hash value is still running, then this is strictly speaking not a problem for the hash_value variable (we could simply abandon the background task and its result), but it is indicative of a programming bug where that background task is now working on invalid memory. To catch these things, the destructor of this class will throw an error if it encounters a situation where the task it is associated with is still running.

The correct solution is to wait in the surrounding class's destructor for the background task to finish before tearing down data structures:

~Complicated() {
hash_value.join();
...tear down data structures...
} // compilers calls hash_value's destructor here

(One could have designed this class in such a way that the destructor waits for the task to complete. But this is error prone, because C++ prescribes that destructors of member variables are called in reverse order in which they are declared, and so if this destructor is called by the compiler as part of a surrounding class's destructor, the destructors of all member variables have already run by the time we get to wait for the background task – but the background task is likely still working on these data members, and that presents a probable bug. Instead, we force the destructor of the surrounding class to explicitly wait for the task to complete before reaching the end of the destructor at which the compiler inserts the calls to the destructors of the class's member variables.)

Definition at line 332 of file task_result.h.

Member Function Documentation

◆ operator=()

template<typename T >
void Threads::TaskResult< T >::operator= ( const Task< T > &  t)
inline

Copy assignment operator from a Task object. By assigning the Task object to the current object, the current object is set to represent the result of that task.

When calling this operator, the current object will no longer represent the result of a previous Task. If the previously associated task is still running, this function throws an exception. This is because assigning a new task to this object when the previous task is still running is likely a bug: In most cases, the new and old tasks are both operating on the state of another object; the fact that the old task is still running often indicates that it is working on data that has been changed underneath. To illustrate this, case, consider the following code snippet:

class Complicated {
public:
Complicated () {
...initialize members...;
hash_value = Threads::new_task(
[&]() { compute has value for the current object; });
}
void frobnicate () {
...do something with members...;
hash_value = Threads::new_task(
[&]() { compute has value for the current object; });
}
private:
...complicated data members...
};

Here, in frobnicate(), member variables are updated according to what the frobnicate() operation represents, and then the hash_value variable is updated as well, but in the background. The issue is that if the background task started in the constructor is still running, then that background task is working on data that is being changed beneath it, likely resulting in unpredictable outcomes. The correct approach would therefore be to write frobnicate() as follows:

void frobnicate () {
hash_value.join();
...do something with members...;
hash_value = Threads::new_task(
[&]() { compute has value for the current object; });
}
const T & value() const

The call to join() ensures that the program waits for the previous task to finish before starting to modify the underlying data. (Calling clear() might seem like a useful alternative in that it simply abandons the first background task, but while that ensures that the first task's result are not put into the current object, it still means that that task is working on data that is changing as it is working, with obviously unpredictable results.)

Definition at line 344 of file task_result.h.

◆ clear()

template<typename T >
void Threads::TaskResult< T >::clear ( )
inline

Reset the current object to a state as if it had been default-constructed. For the same reasons as outlined in the documentation of the destructor and of the assignment operator, this function considers it an error (and throws an exception) if there is still a currently running task associated with the object.

Definition at line 363 of file task_result.h.

◆ join()

template<typename T >
void Threads::TaskResult< T >::join ( )
inline

If this object is associated with a task, wait for it to finish. If it isn't associated with a task, just return.

Definition at line 399 of file task_result.h.

◆ value()

template<typename T >
const T & Threads::TaskResult< T >::value ( ) const
inline

Return a reference to the object computed by the task. This is always a const reference to reflect the semantics that this object represents what the Threads::Task computed (which is what it is, and cannot be changed later on).

Definition at line 426 of file task_result.h.

◆ wait_and_move_result()

template<typename T >
void Threads::TaskResult< T >::wait_and_move_result ( ) const
inlineprivate

Wait for the task to finish, move its result into the task_result object, and then release all information still associated with the task that originally computed the result.

Definition at line 437 of file task_result.h.

Member Data Documentation

◆ result_is_available

template<typename T >
std::atomic<bool> Threads::TaskResult< T >::result_is_available
mutableprivate

An atomic flag that allows us to test whether the task has finished and the result is available.

Definition at line 277 of file task_result.h.

◆ task

template<typename T >
std::optional<Task<T> > Threads::TaskResult< T >::task
mutableprivate

An object that references the task that computes the result TaskResult stores. Once the task has finished, and the result has been moved out of the task object and into task_result, the task object is reset – that is, we destroy all traces of references to the Thread::Task that computed the result.

Definition at line 286 of file task_result.h.

◆ task_result

template<typename T >
std::optional<T> Threads::TaskResult< T >::task_result
mutableprivate

The object that results from running the background task.

Definition at line 291 of file task_result.h.

◆ mutex

template<typename T >
std::mutex Threads::TaskResult< T >::mutex
mutableprivate

A lock object that guards access to all of the mutable objects above.

Definition at line 296 of file task_result.h.


The documentation for this class was generated from the following file: