class
#include <taskflow/core/taskflow.hpp>
Taskflow class to create a taskflow object
A taskflow manages a task dependency graph where each task represents a callable object (e.g., lambda, std::
- static task : the callable constructible from
std::
function<void()> - subflow task : the callable constructible from
std::
function<void(tf:: Subflow&)> - condition task : the callable constructible from
std::
function<int()> - multi-condition task: the callable constructible from
std::function<tf::SmallVector<int>()>
- 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
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 in this taskflow
- auto empty() const -> bool
- queries if this taskflow is empty (has no tasks)
-
void name(const std::
string&) - assigns a new name to this taskflow
-
auto name() const -> const std::
string& - 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 taskto
- 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());
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());
void tf:: Taskflow:: dump(std:: ostream& ostream) const
dumps the taskflow to a DOT format through a std::
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::
This method is similar to tf::Taskflow::dump(std::ostream& ostream), but returning a string of the graph in DOT format.
size_t tf:: Taskflow:: num_tasks() const
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::
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);
bool tf:: Taskflow:: empty() const
queries if this taskflow is empty (has no tasks)
An empty taskflow has no tasks, i.e., the return of tf::0
.
tf::Taskflow taskflow; assert(taskflow.empty() == true); taskflow.emplace([](){}); assert(taskflow.empty() == false);
void tf:: Taskflow:: name(const std:: string&)
assigns a new name to this taskflow
taskflow.name("foo"); assert(taskflow.name() == "foo");
const std:: string& tf:: Taskflow:: name() const
queries the name of this taskflow
tf::Taskflow taskflow("foo"); assert(taskflow.name() == "foo");
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 this taskflow
A visitor is a callable that takes an argument of type tf::
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) |
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);
Graph& tf:: Taskflow:: graph()
returns a reference to the underlying graph object
A graph object is of type tf::