Task class
class to create a task handle over a taskflow node
A task points to a node in a taskflow graph and provides a set of methods for users to access and modify attributes of the associated node, such as dependencies, callable, names, and so on. A task is a very lightweight object (i.e., it only stores a node pointer) and can be trivially copied around.
// create two tasks with one dependency auto task1 = taskflow.emplace([](){}).name("task1"); auto task2 = taskflow.emplace([](){}).name("task2"); task1.precede(task2); // dump the task information through std::cout task1.dump(std::cout);
A task created from a taskflow can be one of the following types:
- tf::TaskType:: STATIC - Static Tasking 
- tf::TaskType:: CONDITION - Conditional Tasking 
- tf::TaskType:: RUNTIME - Runtime Tasking 
- tf::TaskType:: SUBFLOW - Subflow Tasking 
- tf::TaskType:: MODULE - Composable Tasking 
tf::Task task1 = taskflow.emplace([](){}).name("static task"); tf::Task task2 = taskflow.emplace([](){ return 3; }).name("condition task"); tf::Task task3 = taskflow.emplace([](tf::Runtime&){}).name("runtime task"); tf::Task task4 = taskflow.emplace([](tf::Subflow& sf){ tf::Task stask1 = sf.emplace([](){}); tf::Task stask2 = sf.emplace([](){}); }).name("subflow task"); tf::Task task5 = taskflow.composed_of(taskflow2).name("module task");
A tf::
tf::Task task = taskflow.emplace([](){}).name("static task"); task.work([](tf::Subflow& sf){ tf::Task stask1 = sf.emplace([](){}); tf::Task stask2 = sf.emplace([](){}); }).name("subflow task");
Constructors, destructors, conversion operators
Public functions
- auto operator=(const Task& other) -> Task&
- replaces the contents with a copy of the other task
- 
              auto operator=(std::nullptr_t) -> Task& 
- replaces the contents with a null pointer
- auto operator==(const Task& rhs) const -> bool
- compares if two tasks are associated with the same taskflow node
- auto operator!=(const Task& rhs) const -> bool
- compares if two tasks are not associated with the same taskflow node
- 
              auto name() const -> const std::string& 
- queries the name of the task
- auto num_successors() const -> size_t
- queries the number of successors of the task
- auto num_predecessors() const -> size_t
- queries the number of predecessors of the task
- auto num_strong_dependencies() const -> size_t
- queries the number of strong dependencies of the task
- auto num_weak_dependencies() const -> size_t
- queries the number of weak dependencies of the task
- 
              auto name(const std::string& name) -> Task& 
- assigns a name to the task
- 
              template<typename C>auto work(C&& callable) -> Task&
- assigns a callable
- 
              template<typename T>auto composed_of(T& object) -> Task&
- creates a module task from a taskflow
- 
              template<typename... Ts>auto precede(Ts && ... tasks) -> Task&
- adds precedence links from this to other tasks
- 
              template<typename... Ts>auto succeed(Ts && ... tasks) -> Task&
- adds precedence links from other tasks to this
- 
              template<typename... Ts>auto remove_predecessors(Ts && ... tasks) -> Task&
- removes predecessor links from other tasks to this
- 
              template<typename... Ts>auto remove_successors(Ts && ... tasks) -> Task&
- removes successor links from this to other tasks
- auto release(Semaphore& semaphore) -> Task&
- makes the task release the given semaphore
- 
              template<typename I>auto release(I first, I last) -> Task&
- makes the task release the given range of semaphores
- auto acquire(Semaphore& semaphore) -> Task&
- makes the task acquire the given semaphore
- 
              template<typename I>auto acquire(I first, I last) -> Task&
- makes the task acquire the given range of semaphores
- auto data(void* data) -> Task&
- assigns pointer to user data
- void reset()
- resets the task handle to null
- void reset_work()
- resets the associated work to a placeholder
- auto empty() const -> bool
- queries if the task handle is associated with a taskflow node
- auto has_work() const -> bool
- queries if the task has a work assigned
- 
              template<typename V>void for_each_successor(V&& visitor) const
- applies an visitor callable to each successor of the task
- 
              template<typename V>void for_each_predecessor(V&& visitor) const
- applies an visitor callable to each predecessor of the task
- 
              template<typename V>void for_each_subflow_task(V&& visitor) const
- applies an visitor callable to each subflow task
- auto hash_value() const -> size_t
- obtains a hash value of the underlying node
- auto type() const -> TaskType
- returns the task type
- 
              void dump(std::ostream& ostream) const 
- dumps the task through an output stream
- auto data() const -> void*
- queries pointer to user data
- 
              auto exception_ptr() const -> std::exception_ptr 
- retrieves the exception pointer of this task
- auto has_exception_ptr() const -> bool
- queries if the task has an exception pointer
Function documentation
               tf::
            constructs an empty task
An empty task is not associated with any node in a taskflow.
              Task& tf::
            replaces the contents with a null pointer
tf::Task A = taskflow.emplace([](){ std::cout << "A\n"; }); A = nullptr; // A no longer refers to any node
              bool tf::
            compares if two tasks are associated with the same taskflow node
| Parameters | |
|---|---|
| rhs | the other task to compare with | 
| Returns | true if both tasks refer to the same node; false otherwise | 
tf::Task A = taskflow.emplace([](){ std::cout << "A\n"; }); tf::Task B = A; assert(A == B); // A and B refer to the same node
              bool tf::
            compares if two tasks are not associated with the same taskflow node
| Parameters | |
|---|---|
| rhs | the other task to compare with | 
| Returns | true if they refer to different nodes; false otherwise | 
tf::Task A = taskflow.emplace([](){ std::cout << "A\n"; }); tf::Task B = taskflow.emplace([](){ std::cout << "B\n"; }); assert(A != B); // A and B refer to different nodes
              const std::
            queries the name of the task
| Returns | the name of the task as a constant string reference | 
|---|
tf::Task task = taskflow.emplace([](){}); task.name("MyTask"); std::cout << "Task name: " << task.name() << std::endl;
              size_t tf::
            queries the number of successors of the task
| Returns | the number of successor tasks. | 
|---|
tf::Task A = taskflow.emplace([](){}); tf::Task B = taskflow.emplace([](){}); A.precede(B); // B is a successor of A std::cout << "A has " << A.num_successors() << " successor(s)." << std::endl;
              size_t tf::
            queries the number of predecessors of the task
| Returns | the number of predecessor tasks | 
|---|
tf::Task A = taskflow.emplace([](){}); tf::Task B = taskflow.emplace([](){}); A.precede(B); // A is a predecessor of B std::cout << "B has " << B.num_predecessors() << " predecessor(s)." << std::endl;
              size_t tf::
            queries the number of strong dependencies of the task
| Returns | the number of strong dependencies to this task | 
|---|
A strong dependency is a preceding link from one non-condition task to another task. For instance, task cond below has one strong dependency, while tasks yes and no each have one weak dependency.
auto [init, cond, yes, no] = taskflow.emplace( [] () { }, [] () { return 0; }, [] () { std::cout << "yes\n"; }, [] () { std::cout << "no\n"; } ); cond.succeed(init) .precede(yes, no); // executes yes if cond returns 0 // executes no if cond returns 1
              size_t tf::
            queries the number of weak dependencies of the task
| Returns | the number of weak dependencies to this task | 
|---|
A weak dependency is a preceding link from one condition task to another task. For instance, task cond below has one strong dependency, while tasks yes and no each have one weak dependency.
auto [init, cond, yes, no] = taskflow.emplace( [] () { }, [] () { return 0; }, [] () { std::cout << "yes\n"; }, [] () { std::cout << "no\n"; } ); cond.succeed(init) .precede(yes, no); // executes yes if cond returns 0 // executes no if cond returns 1
              Task& tf::
            assigns a name to the task
| Parameters | |
|---|---|
| name | a std:: | 
| Returns | *this | 
tf::Task task = taskflow.emplace([](){}).name("foo"); assert(task.name*) == "foo");
              
                template<typename C>
              
              Task& tf::
            assigns a callable
| Template parameters | |
|---|---|
| C | callable type | 
| Parameters | |
| callable | callable to construct a task | 
| Returns | *this | 
A tf::
tf::Task task = taskflow.emplace([](){}).name("static task"); task.work([](tf::Subflow& sf){ tf::Task stask1 = sf.emplace([](){}); tf::Task stask2 = sf.emplace([](){}); }).name("subflow task");
              
                template<typename T>
              
              Task& tf::
            creates a module task from a taskflow
| Template parameters | |
|---|---|
| T | object type | 
| Parameters | |
| object | a custom object that defines T::graph()method | 
| Returns | *this | 
The example below creates a module task from a taskflow:
task.composed_of(taskflow);
To understand how Taskflow schedules a module task including how to create a schedulable graph, pleas refer to Create a Custom Composable Graph.
              
                template<typename... Ts>
              
              Task& tf::
            adds precedence links from this to other tasks
| Template parameters | |
|---|---|
| Ts | parameter pack | 
| Parameters | |
| tasks | one or multiple tasks | 
| Returns | *this | 
The example below creates a taskflow of two tasks, where task1 runs before task2.
auto [task1, task2] = taskflow.emplace( [](){ std::cout << "task1\n"; }, [](){ std::cout << "task2\n"; } ); task1.precede(task2);
              
                template<typename... Ts>
              
              Task& tf::
            adds precedence links from other tasks to this
| Template parameters | |
|---|---|
| Ts | parameter pack | 
| Parameters | |
| tasks | one or multiple tasks | 
| Returns | *this | 
The example below creates a taskflow of two tasks, where task1 runs before task2.
auto [task1, task2] = taskflow.emplace( [](){ std::cout << "task1\n"; }, [](){ std::cout << "task2\n"; } ); task2.succeed(task1);
              
                template<typename... Ts>
              
              Task& tf::
            removes predecessor links from other tasks to this
| Template parameters | |
|---|---|
| Ts | parameter pack | 
| Parameters | |
| tasks | one or multiple tasks | 
| Returns | *this | 
This method removes the dependency links where the given tasks are predecessors of this task (i.e., tasks -> this). It ensures both sides of the dependency are updated to maintain graph consistency.
tf::Task A = taskflow.emplace([](){}); tf::Task B = taskflow.emplace([](){}); tf::Task C = taskflow.emplace([](){}); // create a linear chain of tasks, A->B->C B.succeed(A) .precede(C); assert(B.num_successors() == 1 && C.num_predecessors() == 1); // remove C from B's successor list C.remove_predecessors(B); assert(B.num_successors() == 0 && C.num_predecessors() == 0);
              
                template<typename... Ts>
              
              Task& tf::
            removes successor links from this to other tasks
| Template parameters | |
|---|---|
| Ts | parameter pack | 
| Parameters | |
| tasks | one or multiple tasks | 
| Returns | *this | 
This method removes the dependency links where this task is a predecessor of the given tasks (i.e., this -> tasks). It ensures both sides of the dependency are updated to maintain graph consistency.
tf::Task A = taskflow.emplace([](){}); tf::Task B = taskflow.emplace([](){}); tf::Task C = taskflow.emplace([](){}); // create a linear chain of tasks, A->B->C B.succeed(A) .precede(C); assert(B.num_successors() == 1 && C.num_predecessors() == 1); // remove C from B's successor list B.remove_successors(C); assert(B.num_successors() == 0 && C.num_predecessors() == 0);
              Task& tf::
            assigns pointer to user data
| Parameters | |
|---|---|
| data | pointer to user data | 
| Returns | *this | 
The following example shows how to attach a user data to a task and retrieve it during the execution of the task.
tf::Executor executor; tf::Taskflow taskflow("attach data to a task"); int data; // user data // create a task and attach it a user data auto A = taskflow.placeholder(); A.data(&data).work([A](){ auto d = *static_cast<int*>(A.data()); std::cout << "data is " << d << std::endl; }); // run the taskflow iteratively with changing data for(data = 0; data<10; data++){ executor.run(taskflow).wait(); }
              void tf::
            resets the task handle to null
Resetting a task will remove its associated taskflow node and make it an empty task.
tf::Task task = taskflow.emplace([](){}); assert(task.empty() == false); task.reset(); assert(task.empty() == true);
              bool tf::
            queries if the task handle is associated with a taskflow node
| Returns | trueif the task is not associated with any taskflow node; otherwisefalse | 
|---|
tf::Task task; assert(task.empty() == true);
Note that an empty task is not equal to a placeholder task. A placeholder task is created from tf::
              bool tf::
            queries if the task has a work assigned
| Returns | trueif the task has a work assigned (not placeholder); otherwisefalse | 
|---|
tf::Task task = taskflow.placeholder(); assert(task.has_work() == false); // assign a static task callable to this task task.work([](){}); assert(task.has_work() == true);
              
                template<typename V>
              
              void tf::
            applies an visitor callable to each successor of the task
| Template parameters | |
|---|---|
| V | a callable type (function, lambda, etc.) that accepts a tf:: | 
| Parameters | |
| visitor | visitor to apply to each subflow task | 
This method allows you to traverse and inspect successor tasks of this task. For instance, the code below iterates the two successors (task2 and task3) of task1.
auto [task1, task2, task3] = taskflow.emplace( [](){ std::cout << "task 1\n"; }, [](){ std::cout << "task 2\n"; }, [](){ std::cout << "task 3\n"; } }); task1.precede(task2, task3); task1.for_each_successor([](tf::Task successor){ std::cout << "successor task " << successor.name() << '\n'; });
              
                template<typename V>
              
              void tf::
            applies an visitor callable to each predecessor of the task
| Template parameters | |
|---|---|
| V | a callable type (function, lambda, etc.) that accepts a tf:: | 
| Parameters | |
| visitor | visitor to apply to each predecessor task | 
This method allows you to traverse and inspect predecessor tasks of this task. For instance, the code below iterates the two predecessors (task2 and task3) of task1.
auto [task1, task2, task3] = taskflow.emplace( [](){ std::cout << "task 1\n"; }, [](){ std::cout << "task 2\n"; }, [](){ std::cout << "task 3\n"; } }); task1.succeed(task2, task3); task1.for_each_predecessor([](tf::Task predecessor){ std::cout << "predecessor task " << predecessor.name() << '\n'; });
              
                template<typename V>
              
              void tf::
            applies an visitor callable to each subflow task
| Template parameters | |
|---|---|
| V | a callable type (function, lambda, etc.) that accepts a tf:: | 
| Parameters | |
| visitor | visitor to apply to each subflow task | 
This method allows you to traverse and inspect tasks within a subflow. It only applies to a subflow task.
tf::Task task = taskflow.emplace([](tf::Subflow& sf){ tf::Task stask1 = sf.emplace([](){}).name("stask1"); tf::Task stask2 = sf.emplace([](){}).name("stask2"); }); // Iterate tasks in the subflow and print each subflow task. task.for_each_subflow_task([](tf::Task stask){ std::cout << "subflow task " << stask.name() << '\n'; });
              size_t tf::
            obtains a hash value of the underlying node
| Returns | the hash value of the underlying node | 
|---|
The method returns std::
tf::Task task = taskflow.emplace([](){}); std::cout << "hash value of task is " << task.hash_value() << '\n';
              TaskType tf::
            returns the task type
A task can be one of the types defined in tf::
auto task = taskflow.emplace([](){}).name("task"); std::cout << task.name() << " type=[" << tf::to_string(task.type()) << "]\n";
              void tf::
            dumps the task through an output stream
The method dumps the name and the type of this task through the given output stream.
task.dump(std::cout);
              void* tf::
            queries pointer to user data
| Returns | C-styled pointer to the attached user data by tf:: | 
|---|
The following example shows how to attach a user data to a task and retrieve it during the execution of the task.
tf::Executor executor; tf::Taskflow taskflow("attach data to a task"); int data; // user data // create a task and attach it a user data auto A = taskflow.placeholder(); A.data(&data).work([A](){ auto d = *static_cast<int*>(A.data()); std::cout << "data is " << d << std::endl; }); // run the taskflow iteratively with changing data for(data = 0; data<10; data++){ executor.run(taskflow).wait(); }
              std::
            retrieves the exception pointer of this task
This method retrieves the exception pointer of this task that are silently caught by the executor, if any. When multiple tasks throw exceptions concurrently, only one exception will be propagated, while the others are silently caught and stored within their respective tasks. For example, in the code below, both tasks B and C throw exceptions. However, only one of them will be propagated to the try-catch block, while the other will be silently caught and stored within its respective task.
tf::Executor executor(2); tf::Taskflow taskflow; std::atomic<size_t> arrivals(0); auto [B, C] = taskflow.emplace( [&]() { // wait for two threads to arrive so we avoid premature cancellation ++arrivals; while(arrivals != 2); throw std::runtime_error("oops"); }, [&]() { // wait for two threads to arrive so we avoid premature cancellation ++arrivals; while(arrivals != 2); throw std::runtime_error("oops"); } ); try { executor.run(taskflow).get(); } catch (const std::runtime_error& e) { std::cerr << e.what(); } // exactly one holds an exception as another was propagated to the try-catch block assert((B.exception_ptr() != nullptr) != (C.exception_ptr() != nullptr));
              bool tf::
            queries if the task has an exception pointer
The method checks whether the task holds a pointer to a silently caught exception.