docs/classtf_1_1Task.html
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 dependencyauto task1 = taskflow.emplace([](){}).name("task1");auto task2 = taskflow.emplace([](){}).name("task2");task1.precede(task2);// dump the task information through std::couttask1.dump(std::cout);
A task created from a taskflow can be one of the following types:
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::Task is polymorphic. Once created, you can assign a different task type to it using tf::Task::work. For example, the code below creates a static task and then reworks it to a subflow task:
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");
Task() defaultedconstructs an empty taskTask(const Task& other)constructs the task with the copy of the other task
auto operator=(const Task& other) -> Task&replaces the contents with a copy of the other taskauto operator=(std::nullptr_t) -> Task&replaces the contents with a null pointerauto operator==(const Task& rhs) const -> boolcompares if two tasks are associated with the same taskflow nodeauto operator!=(const Task& rhs) const -> boolcompares if two tasks are not associated with the same taskflow nodeauto name() const -> const std::string&queries the name of the taskauto num_successors() const -> size_tqueries the number of successors of the taskauto num_predecessors() const -> size_tqueries the number of predecessors of the taskauto num_strong_dependencies() const -> size_tqueries the number of strong dependencies of the taskauto num_weak_dependencies() const -> size_tqueries the number of weak dependencies of the taskauto 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 tasksauto 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 semaphoresauto 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 semaphoresauto data(void* data) -> Task&assigns pointer to user datavoid reset()resets the task handle to nullvoid reset_work()resets the associated work to a placeholderauto empty() const -> boolqueries if the task handle is associated with a taskflow nodeauto has_work() const -> boolqueries if the task has a work assigned template<typename V> void for_each_successor(V&& visitor) constapplies an visitor callable to each successor of the task template<typename V> void for_each_predecessor(V&& visitor) constapplies an visitor callable to each predecessor of the task template<typename V> void for_each_subflow_task(V&& visitor) constapplies an visitor callable to each subflow taskauto hash_value() const -> size_tobtains a hash value of the underlying nodeauto type() const -> TaskTypereturns the task typevoid dump(std::ostream& ostream) constdumps the task through an output streamauto data() const -> void*queries pointer to user dataauto exception_ptr() const -> std::exception_ptrretrieves the exception pointer of this taskauto has_exception_ptr() const -> boolqueries if the task has an exception pointer
constructs an empty task
An empty task is not associated with any node in a taskflow.
constructs the task with the copy of the other task
| Parameters |
|---|
| other |
tf::Taskflow taskflow;tf::Task A = taskflow.emplace([](){ std::cout \<\< "Task A\n"; });tf::Task B(A);assert(B == A); // Now, B and A refer to the same underlying node
replaces the contents with a copy of the other task
| Parameters |
|---|
| other |
tf::Task A = taskflow.emplace([](){ std::cout \<\< "A\n"; });tf::Task B;B = A;// B now refers to the same node as A
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
compares if two tasks are associated with the same taskflow node
| Parameters |
|---|
| rhs |
| Returns |
tf::Task A = taskflow.emplace([](){ std::cout \<\< "A\n"; });tf::Task B = A;assert(A == B);// A and B refer to the same node
compares if two tasks are not associated with the same taskflow node
| Parameters |
|---|
| rhs |
| Returns |
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
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;
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 Astd::cout \<\< "A has " \<\< A.num\_successors() \<\< " successor(s)." \<\< std::endl;
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 Bstd::cout \<\< "B has " \<\< B.num\_predecessors() \<\< " predecessor(s)." \<\< std::endl;
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
Taskflowp0x7f9e1e700030initp0x7f9e1e700140condp0x7f9e1e700030->p0x7f9e1e700140p0x7f9e1e700250yesp0x7f9e1e700140->p0x7f9e1e7002500p0x7f9e1e700360nop0x7f9e1e700140->p0x7f9e1e7003601
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
Taskflowp0x7f9e1e700030initp0x7f9e1e700140condp0x7f9e1e700030->p0x7f9e1e700140p0x7f9e1e700250yesp0x7f9e1e700140->p0x7f9e1e7002500p0x7f9e1e700360nop0x7f9e1e700140->p0x7f9e1e7003601
assigns a name to the task
| Parameters |
|---|
| name |
| Returns |
tf::Task task = taskflow.emplace([](){}).name("foo");assert(task.name\*) == "foo");
assigns a callable
| Template parameters |
|---|
| C |
| Parameters |
| --- |
| callable |
| Returns |
A tf::Task is polymorphic. Once created, you can reassign it to a different callable of a different task type using tf::Task::work. For example, the code below creates a static task and reworks it to a subflow task:
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");
creates a module task from a taskflow
| Template parameters |
|---|
| T |
| Parameters |
| --- |
| object |
| Returns |
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.
adds precedence links from this to other tasks
| Template parameters |
|---|
| Ts |
| Parameters |
| --- |
| tasks |
| Returns |
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);
adds precedence links from other tasks to this
| Template parameters |
|---|
| Ts |
| Parameters |
| --- |
| tasks |
| Returns |
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);
removes predecessor links from other tasks to this
| Template parameters |
|---|
| Ts |
| Parameters |
| --- |
| tasks |
| Returns |
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-\>CB.succeed(A) .precede(C);assert(B.num\_successors() == 1 && C.num\_predecessors() == 1);// remove C from B's successor listC.remove\_predecessors(B);assert(B.num\_successors() == 0 && C.num\_predecessors() == 0);
removes successor links from this to other tasks
| Template parameters |
|---|
| Ts |
| Parameters |
| --- |
| tasks |
| Returns |
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-\>CB.succeed(A) .precede(C);assert(B.num\_successors() == 1 && C.num\_predecessors() == 1);// remove C from B's successor listB.remove\_successors(C);assert(B.num\_successors() == 0 && C.num\_predecessors() == 0);
makes the task release the given semaphore
makes the task release the given range of semaphores
makes the task acquire the given semaphore
makes the task acquire the given range of semaphores
assigns pointer to user data
| Parameters |
|---|
| data |
| Returns |
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 dataauto 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 datafor(data = 0; data\<10; data++){executor.run(taskflow).wait();}
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);
queries if the task handle is associated with a taskflow node
| Returns | true if the task is not associated with any taskflow node; otherwise false |
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::Taskflow::placeholder and is associated with a taskflow node, but its work is not assigned yet.
queries if the task has a work assigned
| Returns | true if the task has a work assigned (not placeholder); otherwise false |
tf::Task task = taskflow.placeholder();assert(task.has\_work() == false);// assign a static task callable to this tasktask.work([](){});assert(task.has\_work() == true);
applies an visitor callable to each successor of the task
| Template parameters |
|---|
| V |
| Parameters |
| --- |
| visitor |
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';});
applies an visitor callable to each predecessor of the task
| Template parameters |
|---|
| V |
| Parameters |
| --- |
| visitor |
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';});
applies an visitor callable to each subflow task
| Template parameters |
|---|
| V |
| Parameters |
| --- |
| visitor |
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';});
obtains a hash value of the underlying node
| Returns | the hash value of the underlying node |
The method returns std::hash on the underlying node pointer.
tf::Task task = taskflow.emplace([](){});std::cout \<\< "hash value of task is " \<\< task.hash\_value() \<\< '\n';
returns the task type
A task can be one of the types defined in tf::TaskType and can be printed in a human-readable form using tf::to_string.
auto task = taskflow.emplace([](){}).name("task");std::cout \<\< task.name() \<\< " type=[" \<\< tf::to\_string(task.type()) \<\< "]\n";
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);
queries pointer to user data
| Returns | C-styled pointer to the attached user data by tf::Task::data(void* data) |
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 dataauto 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 datafor(data = 0; data\<10; data++){executor.run(taskflow).wait();}
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 blockassert((B.exception\_ptr() != nullptr) != (C.exception\_ptr() != nullptr));
queries if the task has an exception pointer
The method checks whether the task holds a pointer to a silently caught exception.