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 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::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() defaulted
constructs an empty task
Task(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 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
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

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 the other task to copy
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 the other task to copy
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 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::Task::operator!=(const Task& rhs) const

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::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 A
std::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 B
std::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
Taskflow p0x7f9e1e700030 init p0x7f9e1e700140 cond p0x7f9e1e700030->p0x7f9e1e700140 p0x7f9e1e700250 yes p0x7f9e1e700140->p0x7f9e1e700250 0 p0x7f9e1e700360 no p0x7f9e1e700140->p0x7f9e1e700360 1

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
Taskflow p0x7f9e1e700030 init p0x7f9e1e700140 cond p0x7f9e1e700030->p0x7f9e1e700140 p0x7f9e1e700250 yes p0x7f9e1e700140->p0x7f9e1e700250 0 p0x7f9e1e700360 no p0x7f9e1e700140->p0x7f9e1e700360 1

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

assigns a name to the task

Parameters
name a std::string
Returns *this
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 callable type
Parameters
callable callable to construct a task
Returns *this

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 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::Task::precede(Ts && ... tasks)

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::Task::succeed(Ts && ... tasks)

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

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 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::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 task
task.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 a callable type (function, lambda, etc.) that accepts a tf::Task handle
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::Task::for_each_predecessor(V&& visitor) const

applies an visitor callable to each predecessor of the task

Template parameters
V a callable type (function, lambda, etc.) that accepts a tf::Task handle
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::Task::for_each_subflow_task(V&& visitor) const

applies an visitor callable to each subflow task

Template parameters
V a callable type (function, lambda, etc.) that accepts a tf::Task handle
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::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 std::cout.

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