tf::Taskflow class

class to create a taskflow object

A taskflow manages a task dependency graph where each task represents a callable object (e.g., lambda, std::function) and an edge represents a dependency between two tasks. A task is one of the following types:

  1. static task : the callable constructible from std::function<void()>
  2. subflow task : the callable constructible from std::function<void(tf::Subflow&)>
  3. condition task : the callable constructible from std::function<int()>
  4. multi-condition task: the callable constructible from std::function<tf::SmallVector<int>()>
  5. module task : the task constructed from tf::Taskflow::composed_of std::function<void(tf::Runtime&)>

Each task is a basic computation unit and is run by one worker thread from an executor. The following example creates a simple taskflow graph of four static tasks, A, B, C, and D, where A runs before B and C and D runs after B and C.

tf::Executor executor;
tf::Taskflow taskflow("simple");

tf::Task A = taskflow.emplace([](){ std::cout << "TaskA\n"; });
tf::Task B = taskflow.emplace([](){ std::cout << "TaskB\n"; });
tf::Task C = taskflow.emplace([](){ std::cout << "TaskC\n"; });
tf::Task D = taskflow.emplace([](){ std::cout << "TaskD\n"; });

A.precede(B, C);  // A runs before B and C
D.succeed(B, C);  // D runs after  B and C

executor.run(taskflow).wait();

The taskflow object itself is NOT thread-safe. You should not modifying the graph while it is running, such as adding new tasks, adding new dependencies, and moving the taskflow to another. To minimize the overhead of task creation, our runtime leverages a global object pool to recycle tasks in a thread-safe manner.

Please refer to Cookbook to learn more about each task type and how to submit a taskflow to an executor.

Base classes

class FlowBuilder
class to build a task dependency graph

Constructors, destructors, conversion operators

Taskflow(const std::string& name)
constructs a taskflow with the given name
Taskflow()
constructs a taskflow
Taskflow(Taskflow&& rhs)
constructs a taskflow from a moved taskflow
~Taskflow() defaulted
default destructor

Public functions

auto operator=(Taskflow&& rhs) -> Taskflow&
move assignment operator
void dump(std::ostream& ostream) const
dumps the taskflow to a DOT format through a std::ostream target
auto dump() const -> std::string
dumps the taskflow to a std::string of DOT format
auto num_tasks() const -> size_t
queries the number of tasks
auto empty() const -> bool
queries the emptiness of the taskflow
void name(const std::string&)
assigns a name to the taskflow
auto name() const -> const std::string&
queries the name of the taskflow
void clear()
clears the associated task dependency graph
template<typename V>
void for_each_task(V&& visitor) const
applies a visitor to each task in the taskflow
void remove_dependency(Task from, Task to)
removes dependencies that go from task from to task to
auto graph() -> Graph&
returns a reference to the underlying graph object

Function documentation

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

constructs a taskflow with the given name

tf::Taskflow taskflow("My Taskflow");
std::cout << taskflow.name();         // "My Taskflow"

tf::Taskflow::Taskflow(Taskflow&& rhs)

constructs a taskflow from a moved taskflow

Constructing a taskflow taskflow1 from a moved taskflow taskflow2 will migrate the graph of taskflow2 to taskflow1. After the move, taskflow2 will become empty.

tf::Taskflow taskflow1(std::move(taskflow2));
assert(taskflow2.empty());

Notice that taskflow2 should not be running in an executor during the move operation, or the behavior is undefined.

tf::Taskflow::~Taskflow() defaulted

default destructor

When the destructor is called, all tasks and their associated data (e.g., captured data) will be destroyed. It is your responsibility to ensure all submitted execution of this taskflow have completed before destroying it. For instance, the following code results in undefined behavior since the executor may still be running the taskflow while it is destroyed after the block.

{
  tf::Taskflow taskflow;
  executor.run(taskflow);
}

To fix the problem, we must wait for the execution to complete before destroying the taskflow.

{
  tf::Taskflow taskflow;
  executor.run(taskflow).wait();
}

Taskflow& tf::Taskflow::operator=(Taskflow&& rhs)

move assignment operator

Moving a taskflow taskflow2 to another taskflow taskflow1 will destroy the existing graph of taskflow1 and assign it the graph of taskflow2. After the move, taskflow2 will become empty.

taskflow1 = std::move(taskflow2);
assert(taskflow2.empty());

Notice that both taskflow1 and taskflow2 should not be running in an executor during the move operation, or the behavior is undefined.

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

dumps the taskflow to a DOT format through a std::ostream target

taskflow.dump(std::cout);  // dump the graph to the standard output

std::ofstream ofs("output.dot");
taskflow.dump(ofs);        // dump the graph to the file output.dot

For dynamically spawned tasks, such as module tasks, subflow tasks, and GPU tasks, you need to run the taskflow first before you can dump the entire graph.

tf::Task parent = taskflow.emplace([](tf::Subflow sf){
  sf.emplace([](){ std::cout << "child\n"; });
});
taskflow.dump(std::cout);      // this dumps only the parent tasks
executor.run(taskflow).wait();
taskflow.dump(std::cout);      // this dumps both parent and child tasks

std::string tf::Taskflow::dump() const

dumps the taskflow to a std::string of DOT format

This method is similar to tf::Taskflow::dump(std::ostream& ostream), but returning a string of the graph in DOT format.

bool tf::Taskflow::empty() const

queries the emptiness of the taskflow

An empty taskflow has no tasks. That is the return of tf::Taskflow::num_tasks is zero.

void tf::Taskflow::name(const std::string&)

assigns a name to the taskflow

taskflow.name("assign another name");

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

queries the name of the taskflow

std::cout << "my name is: " << taskflow.name();

void tf::Taskflow::clear()

clears the associated task dependency graph

When you clear a taskflow, all tasks and their associated data (e.g., captured data in task callables) will be destroyed. The behavior of clearing a running taskflow is undefined.

template<typename V>
void tf::Taskflow::for_each_task(V&& visitor) const

applies a visitor to each task in the taskflow

A visitor is a callable that takes an argument of type tf::Task and returns nothing. The following example iterates each task in a taskflow and prints its name:

taskflow.for_each_task([](tf::Task task){
  std::cout << task.name() << '\n';
});

void tf::Taskflow::remove_dependency(Task from, Task to)

removes dependencies that go from task from to task to

Parameters
from from task (dependent)
to to task (successor)
tf::Taskflow taskflow;
auto a = taskflow.placeholder().name("a");
auto b = taskflow.placeholder().name("b");
auto c = taskflow.placeholder().name("c");
auto d = taskflow.placeholder().name("d");

a.precede(b, c, d);
assert(a.num_successors() == 3);
assert(b.num_dependents() == 1);
assert(c.num_dependents() == 1);
assert(d.num_dependents() == 1);

taskflow.remove_dependency(a, b);
assert(a.num_successors() == 2);
assert(b.num_dependents() == 0);

Graph& tf::Taskflow::graph()

returns a reference to the underlying graph object

A graph object (of type tf::Graph) is the ultimate storage for the task dependency graph and should only be used as an opaque data structure to interact with the executor (e.g., composition).