| Taskflow: A General-purpose Task-parallel Programming System

Loading...
Searching...
No Matches
Public Member Functions | Friends | List of all members
tf::Taskflow Class Reference

class to create a taskflow object More...

`#include <taskflow/core/taskflow.hpp>`

Inheritance diagram for tf::Taskflow:
[legend]
Collaboration diagram for tf::Taskflow:
[legend]

## Public Member Functions
| 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 & | operator= (Taskflow &&rhs)
| move assignment operator
| ~Taskflow ()=default
| default destructor
void | dump (std::ostream &ostream) const
| dumps the taskflow to a DOT format through a std::ostream target
std::string | dump () const
| dumps the taskflow to a std::string of DOT format
size_t | num_tasks () const
| queries the number of tasks in this taskflow
bool | empty () const
| queries if this taskflow is empty (has no tasks)
void | name (const std::string &)
| assigns a new name to this taskflow
const std::string & | name () const
| queries the name of this 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 this taskflow
void | remove_dependency (Task from, Task to)
| removes dependencies that go from task `from` to task `to`
Graph & | graph ()
| returns a reference to the underlying graph object
Public Member Functions inherited from tf::FlowBuilder
| FlowBuilder (Graph &graph)
| constructs a flow builder with a graph
template<StaticTask C>
Task | emplace (C &&callable)
| creates a static task
template<RuntimeTask C>
Task | emplace (C &&callable)
| creates a runtime task
template<SubflowTask C>
Task | emplace (C &&callable)
| creates a dynamic task
template<ConditionTask C>
Task | emplace (C &&callable)
| creates a condition task
template<MultiConditionTask C>
Task | emplace (C &&callable)
| creates a multi-condition task
template<typename... C> requires (sizeof...(C) > 1)
auto | emplace (C &&... callables)
| creates multiple tasks from a list of callable objects
void | erase (Task task)
| removes a task from a taskflow
template<HasGraph T>
Task | composed_of (T &object)
| creates a module task for the target object
Task | adopt (Graph &&graph)
| creates an adopted module task from the given graph with move semantics
Task | placeholder ()
| creates a placeholder task
void | linearize (std::vector< Task > &tasks)
| adds adjacent dependency links to a linear list of tasks
void | linearize (std::initializer_list< Task > tasks)
| adds adjacent dependency links to a linear list of tasks
template<typename B, typename E, typename C, typename P = DefaultPartitioner>
Task | for_each (B first, E last, C callable, P part=P())
| constructs an STL-styled parallel-for task
template<typename B, typename E, typename S, typename C, typename P = DefaultPartitioner>
Task | for_each_index (B first, E last, S step, C callable, P part=P())
| constructs an index-based parallel-for task
template<typename R, typename C, typename P = DefaultPartitioner>
Task | for_each_by_index (R range, C callable, P part=P())
| constructs an index range-based parallel-for task
template<typename B, typename E, typename O, typename C, typename P = DefaultPartitioner> requires Partitioner<std::decay_t<P>>
Task | transform (B first1, E last1, O d_first, C c, P part=P())
| constructs a parallel-transform task
template<typename B1, typename E1, typename B2, typename O, typename C, typename P = DefaultPartitioner> requires (!Partitioner<std::decay_t<C>>)
Task | transform (B1 first1, E1 last1, B2 first2, O d_first, C c, P part=P())
| constructs a parallel-transform task
template<typename B, typename E, typename T, typename O, typename P = DefaultPartitioner>
Task | reduce (B first, E last, T &init, O bop, P part=P())
| constructs an STL-styled parallel-reduction task
template<typename R, typename T, typename L, typename G, typename P = DefaultPartitioner>
Task | reduce_by_index (R range, T &init, L lop, G gop, P part=P())
| constructs an index range-based parallel-reduction task
template<typename B, typename E, typename T, typename BOP, typename UOP, typename P = DefaultPartitioner> requires Partitioner<std::decay_t<P>>
Task | transform_reduce (B first, E last, T &init, BOP bop, UOP uop, P part=P())
| constructs an STL-styled parallel transform-reduce task
template<typename B1, typename E1, typename B2, typename T, typename BOP_R, typename BOP_T, typename P = DefaultPartitioner> requires (!Partitioner<std::decay_t<BOP_T>>)
Task | transform_reduce (B1 first1, E1 last1, B2 first2, T &init, BOP_R bop_r, BOP_T bop_t, P part=P())
| constructs an STL-styled parallel transform-reduce task
template<typename B, typename E, typename D, typename BOP>
Task | inclusive_scan (B first, E last, D d_first, BOP bop)
| creates an STL-styled parallel inclusive-scan task
template<typename B, typename E, typename D, typename BOP, typename T>
Task | inclusive_scan (B first, E last, D d_first, BOP bop, T init)
| creates an STL-styled parallel inclusive-scan task with an initial value
template<typename B, typename E, typename D, typename T, typename BOP>
Task | exclusive_scan (B first, E last, D d_first, T init, BOP bop)
| creates an STL-styled parallel exclusive-scan task
template<typename B, typename E, typename D, typename BOP, typename UOP>
Task | transform_inclusive_scan (B first, E last, D d_first, BOP bop, UOP uop)
| creates an STL-styled parallel transform-inclusive scan task
template<typename B, typename E, typename D, typename BOP, typename UOP, typename T>
Task | transform_inclusive_scan (B first, E last, D d_first, BOP bop, UOP uop, T init)
| creates an STL-styled parallel transform-inclusive scan task
template<typename B, typename E, typename D, typename T, typename BOP, typename UOP>
Task | transform_exclusive_scan (B first, E last, D d_first, T init, BOP bop, UOP uop)
| creates an STL-styled parallel transform-exclusive scan task
template<typename B, typename E, typename T, typename UOP, typename P = DefaultPartitioner>
Task | find_if (B first, E last, T &result, UOP predicate, P part=P())
| constructs a task to perform STL-styled find-if algorithm
template<typename B, typename E, typename T, typename UOP, typename P = DefaultPartitioner>
Task | find_if_not (B first, E last, T &result, UOP predicate, P part=P())
| constructs a task to perform STL-styled find-if-not algorithm
template<typename B, typename E, typename T, typename C, typename P>
Task | min_element (B first, E last, T &result, C comp, P part)
| constructs a task to perform STL-styled min-element algorithm
template<typename B, typename E, typename T, typename C, typename P>
Task | max_element (B first, E last, T &result, C comp, P part)
| constructs a task to perform STL-styled max-element algorithm
template<typename B, typename E, typename C>
Task | sort (B first, E last, C cmp)
| constructs a dynamic task to perform STL-styled parallel sort
template<typename B, typename E>
Task | sort (B first, E last)
| constructs a dynamic task to perform STL-styled parallel sort using the `std::less<T>` comparator, where `T` is the element type
template<typename B1, typename E1, typename B2, typename E2, typename O, typename P = DefaultPartitioner> requires Partitioner<std::decay_t<P>>
Task | merge (B1 first1, E1 last1, B2 first2, E2 last2, O d_first, P part=P())
| merges two sorted ranges into a single sorted output using the `std::less` comparator
template<typename B1, typename E1, typename B2, typename E2, typename O, typename C, typename P = DefaultPartitioner> requires (!Partitioner<std::decay_t<C>>)
Task | merge (B1 first1, E1 last1, B2 first2, E2 last2, O d_first, C cmp, P part=P())
| merges two sorted ranges into a single sorted output using the supplied comparator

## Friends
class | Topology
class | Executor
class | FlowBuilder
class | Subflow

## Additional Inherited Members
Protected Attributes inherited from tf::FlowBuilder
Graph & | _graph
| associated graph object

## Detailed Description

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();
tf::Executor
class to create an executor
Definition executor.hpp:62
tf::Executor::run
tf::Future< void > run(Taskflow &taskflow)
runs a taskflow once
tf::Task
class to create a task handle over a taskflow node
Definition task.hpp:263
tf::Task::succeed
Task & succeed(Ts &&... tasks)
adds precedence links from other tasks to this
Definition task.hpp:955
tf::Task::precede
Task & precede(Ts &&... tasks)
adds precedence links from this to other tasks
Definition task.hpp:947
tf::Taskflow
class to create a taskflow object
Definition taskflow.hpp:64

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.

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

## Constructor & Destructor Documentation

## ◆ Taskflow() [1/2]

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

constructs a taskflow with the given name

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

## ◆ Taskflow() [2/2]

tf::Taskflow::Taskflow | ( | Taskflow && | rhs | ) |
| inline

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

Attention
  You should avoid moving a taskflow that is currently running on an executor. Doing so results in undefined behavior.

## ◆ ~Taskflow()

tf::Taskflow::~Taskflow | ( |  | ) |
| default

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

## Member Function Documentation

## ◆ clear()

void tf::Taskflow::clear | ( |  | ) |
| inline

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.

## ◆ dump() [1/2]

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

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.

## ◆ dump() [2/2]

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

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. For subflow tasks the subflow must be retained, otherwise it is cleared when joined and the child tasks not shown in the DOT graph.

tf::Task parent = taskflow.emplace([](tf::Subflow sf){
sf.emplace([](){ std::cout << "child\n"; });
sf.retain(true);
});
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
tf::FlowBuilder::emplace
Task emplace(C &&callable)
creates a static task
Definition flow_builder.hpp:1352
tf::Subflow
class to construct a subflow graph from the execution of a dynamic task
Definition flow_builder.hpp:1516
tf::Subflow::retain
void retain(bool flag) noexcept
specifies whether to keep the subflow after it is joined
Definition flow_builder.hpp:1625
tf::Task::dump
void dump(std::ostream &ostream) const
dumps the task through an output stream
Definition task.hpp:1171

## ◆ empty()

bool tf::Taskflow::empty | ( |  | ) | const
| inline

queries if this taskflow is empty (has no tasks)

An empty taskflow has no tasks, i.e., the return of tf::Taskflow::num_tasks is `0`.

tf::Taskflow taskflow;
assert(taskflow.empty() == true);
taskflow.emplace([](){});
assert(taskflow.empty() == false);
tf::Taskflow::empty
bool empty() const
queries if this taskflow is empty (has no tasks)
Definition taskflow.hpp:381

## ◆ for_each_task()

template<typename V>

void tf::Taskflow::for_each_task | ( | V && | visitor | ) | const

applies a visitor to each task in this 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';
});
tf::Task::name
const std::string & name() const
queries the name of the task
Definition task.hpp:1077

## ◆ graph()

Graph & tf::Taskflow::graph | ( |  | ) |
| inline

returns a reference to the underlying graph object

A graph object is of type tf::Graph and stores a task dependency graph that can be executed by an tf::Executor.

## ◆ name() [1/2]

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

queries the name of this taskflow

tf::Taskflow taskflow("foo");
assert(taskflow.name() == "foo");

## ◆ name() [2/2]

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

assigns a new name to this taskflow

taskflow.name("foo");
assert(taskflow.name() == "foo");

## ◆ num_tasks()

size_t tf::Taskflow::num_tasks | ( |  | ) | const
| inline

queries the number of tasks in this taskflow

The number of tasks in this taskflow is defined at the first level of hierarchy. Tasks that are created dynamically, such as those via tf::Subflow, are not counted.

tf::Taskflow taskflow;
auto my_task = taskflow.emplace([](){});
assert(taskflow.num_tasks() == 1);
// reassign my_task to a subflow of four tasks
my_task.work([](tf::Subflow& sf){
sf.emplace(
[](){ std::cout << "Task A\n"; },
[](){ std::cout << "Task B\n"; },
[](){ std::cout << "Task C\n"; },
[](){ std::cout << "Task D\n"; }
);
});
// subflow tasks will not be counted
assert(taskflow.num_tasks() == 1);
tf::Taskflow::num_tasks
size_t num_tasks() const
queries the number of tasks in this taskflow
Definition taskflow.hpp:376

## ◆ operator=()

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

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

Attention
  You should avoid moving a taskflow that is currently running on an executor. Doing so results in undefined behavior.

## ◆ remove_dependency()

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

removes dependencies that go from task `from` to task `to`

Parameters

  from | from task (dependent)
  to | to task (successor)

Removing the depencency from task `from` to task `to` is equivalent to removing `to` from the succcessor list of `from` and removing `from` from the predecessor list of `to`.

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_predecessors() == 1);
assert(c.num_predecessors() == 1);
assert(d.num_predecessors() == 1);
taskflow.remove_dependency(a, b);
assert(a.num_successors() == 2);
assert(b.num_predecessors() == 0);
tf::FlowBuilder::placeholder
Task placeholder()
creates a placeholder task
Definition flow_builder.hpp:1416
tf::Taskflow::remove_dependency
void remove_dependency(Task from, Task to)
removes dependencies that go from task from to task to
Definition taskflow.hpp:409

Attention
  For performance reason, Taskflow does not store the graph using linked lists but vectors with contiguous space. Therefore, removing tasks or dependencies incurs linear time complexity proportional to the size of the graph and the dependency count of a task.

The documentation for this class was generated from the following file:

- taskflow/core/taskflow.hpp