Back to Taskflow

tf::Task class

docs/classtf_1_1Task.html

4.0.026.0 KB
Original Source

tf::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 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");

Constructors, destructors, conversion operators

Task() defaultedconstructs an empty taskTask(const Task& other)constructs the task with the copy of the other task

Public functions

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

Function documentation

tf::Task::Task() defaulted

constructs an empty task

An empty task is not associated with any node in a taskflow.

tf::Task::Task(const Task& other)

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

Task& tf::Task::operator=(const Task& other)

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

Task& tf::Task::operator=(std::nullptr_t)

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::Task::operator==(const Task& rhs) const

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

bool tf::Task::operator!=(const Task& rhs) const

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

const std::string& tf::Task::name() const

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::Task::num_successors() const

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;

size_t tf::Task::num_predecessors() const

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;

size_t tf::Task::num_strong_dependencies() const

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

size_t tf::Task::num_weak_dependencies() const

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

Task& tf::Task::name(const std::string& name)

assigns a name to the task

Parameters
name
Returns
tf::Task task = taskflow.emplace([](){}).name("foo");assert(task.name\*) == "foo");

template<typename C> Task& tf::Task::work(C&& callable)

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");

template<typename T> Task& tf::Task::composed_of(T& object)

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.

template<typename... Ts> Task& tf::Task::precede(Ts && ... tasks)

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);

template<typename... Ts> Task& tf::Task::succeed(Ts && ... tasks)

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);

template<typename... Ts> Task& tf::Task::remove_predecessors(Ts && ... tasks)

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);

template<typename... Ts> Task& tf::Task::remove_successors(Ts && ... tasks)

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);

Task& tf::Task::release(Semaphore& semaphore)

makes the task release the given semaphore

template<typename I> Task& tf::Task::release(I first, I last)

makes the task release the given range of semaphores

Task& tf::Task::acquire(Semaphore& semaphore)

makes the task acquire the given semaphore

template<typename I> Task& tf::Task::acquire(I first, I last)

makes the task acquire the given range of semaphores

Task& tf::Task::data(void* data)

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();}

void tf::Task::reset()

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::Task::empty() const

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.

bool tf::Task::has_work() const

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);

template<typename V> void tf::Task::for_each_successor(V&& visitor) const

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';});

template<typename V> void tf::Task::for_each_predecessor(V&& visitor) const

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';});

template<typename V> void tf::Task::for_each_subflow_task(V&& visitor) const

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';});

size_t tf::Task::hash_value() const

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';

TaskType tf::Task::type() const

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";

void tf::Task::dump(std::ostream& ostream) const

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::Task::data() const

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();}

std::exception_ptr tf::Task::exception_ptr() const

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));

bool tf::Task::has_exception_ptr() const

queries if the task has an exception pointer

The method checks whether the task holds a pointer to a silently caught exception.