<para>This chapter demonstrates how to create a static task dependency graph. Static tasking captures the static parallel structure of a decomposition and is defined only by the program itself. It has a flat task hierarchy and cannot spawn new tasks from a running dependency graph.</para><sect1id="StaticTasking_1CreateATaskDependencyGraph">
<title>Create a Task Dependency Graph</title>
<para>A task in Taskflow is a <emphasis>callable</emphasis> object for which the operation <ulinkurl="https://en.cppreference.com/w/cpp/utility/functional/invoke">std::invoke</ulink> is applicable. It can be either a functor, a lambda expression, a bind expression, or a class objects with <computeroutput>operator()</computeroutput> overloaded. All tasks are created from <refrefid="classtf_1_1Taskflow"kindref="compound">tf::Taskflow</ref>, the class that manages a task dependency graph. Taskflow provides two methods, <refrefid="classtf_1_1FlowBuilder_1acab0b4ac82260f47fdb36a3244ee3aaf"kindref="member">tf::Taskflow::placeholder</ref> and <refrefid="classtf_1_1FlowBuilder_1a60d7a666cab71ecfa3010b2efb0d6b57"kindref="member">tf::Taskflow::emplace</ref> to create a task.</para><para><programlistingfilename=".cpp"><codeline><highlightclass="normal">1:<sp/><refrefid="classtf_1_1Taskflow"kindref="compound">tf::Taskflow</ref><sp/>taskflow;</highlight></codeline>
<listitem><para>Line 1 creates a taskflow object, or a <emphasis>graph</emphasis> </para></listitem>
<listitem><para>Line 2 creates a placeholder task without work (i.e., callable) </para></listitem>
<listitem><para>Line 3 creates a task from a given callable object and returns a task handle </para></listitem>
<listitem><para>Lines 5-9 create three tasks in one call using C++ structured binding coupled with <refrefid="cpp/utility/tuple"kindref="compound"external="/home/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">std::tuple</ref></para></listitem>
</itemizedlist>
Each time you create a task, the taskflow object creates a node in the task graph and returns a task handle of type <refrefid="classtf_1_1Task"kindref="compound">tf::Task</ref>. A task handle is a lightweight object that wraps up a particular node in a graph and provides a set of methods for you to assign different attributes to the task such as adding dependencies, naming, and assigning a new work.</para><para><programlistingfilename=".cpp"><codeline><highlightclass="normal"><sp/>1:<sp/><refrefid="classtf_1_1Taskflow"kindref="compound">tf::Taskflow</ref><sp/>taskflow;</highlight></codeline>
<listitem><para>Line 1 creates a taskflow object </para></listitem>
<listitem><para>Lines 2-3 create two tasks A and B </para></listitem>
<listitem><para>Lines 5-6 assign a name and a work to task A, and add a precedence link to task B </para></listitem>
<listitem><para>Line 7 adds a dependency link from A to B </para></listitem>
<listitem><para>Lines 9-14 dump the task attributes</para></listitem>
</itemizedlist>
Taskflow uses general-purpose polymorphic function wrapper, <refrefid="cpp/utility/functional/function"kindref="compound"external="/home/twhuang/Code/taskflow/doxygen/cppreference-doxygen-web.tag.xml">std::function</ref>, to store and invoke a callable in a task. You need to follow its contract to create a task. For example, the callable to construct a task must be copyable, and thus the code below won't compile:</para><para><programlistingfilename=".cpp"><codeline><highlightclass="normal">taskflow.<refrefid="classtf_1_1FlowBuilder_1a60d7a666cab71ecfa3010b2efb0d6b57"kindref="member">emplace</ref>([ptr=std::make_unique<int>(1)](){</highlight></codeline>
<para>You can dump a taskflow to a DOT format and visualize the graph using free online tools such as <ulinkurl="https://dreampuf.github.io/GraphvizOnline/">GraphvizOnline</ulink> and <ulinkurl="http://www.webgraphviz.com/">WebGraphviz</ulink>.</para><para><programlistingfilename=".cpp"><codeline><highlightclass="normal"><sp/>1:<sp/>#include<sp/><taskflow/taskflow.hpp></highlight></codeline>
<para>This example demonstrates how to modify a task's attributes using methods defined in the task handler.</para><para><programlistingfilename=".cpp"><codeline><highlightclass="normal"><sp/>1:<sp/>#include<sp/><taskflow/taskflow.hpp></highlight></codeline>
</programlisting></para><para>The output of this program looks like the following:</para><para><programlistingfilename=".sh"><codeline><highlightclass="normal">This<sp/>is<sp/>Task<sp/>0:<sp/>num_dependents=0,<sp/>num_successors=1</highlight></codeline>
<listitem><para>Line 5 creates a taskflow object </para></listitem>
<listitem><para>Lines 7-10 create two placeholder tasks with no works and stores the corresponding task handles in a vector </para></listitem>
<listitem><para>Lines 12-13 name the two tasks with human-readable strings </para></listitem>
<listitem><para>Line 14 adds a dependency link from the first task to the second task </para></listitem>
<listitem><para>Lines 16-20 print out the name of each task, the number of dependents, and the number of successors </para></listitem>
<listitem><para>Line 22 dumps the task dependency graph to a <ulinkurl="https://dreampuf.github.io/GraphvizOnline/">GraphViz Online</ulink> format (dot) </para></listitem>
<listitem><para>Lines 24-25 assign a new target to each task</para></listitem>
</itemizedlist>
You can change the name and work of a task at anytime before running the graph. The later assignment overwrites the previous values.</para></sect1>
<sect1id="StaticTasking_1TraverseAdjacentTasks">
<title>Traverse Adjacent Tasks</title>
<para>You can iterate the successor list and the dependent list of a task by using <refrefid="classtf_1_1Task_1aff13a503d4a3c994eb08cb6f22e1b427"kindref="member">tf::Task::for_each_successor</ref> and <refrefid="classtf_1_1Task_1a3bf68937662bf291637e4a763476b2e4"kindref="member">tf::Task::for_each_dependent</ref>, respectively. Each method takes a lambda and applies it to a successor or a dependent being traversed.</para><para><programlistingfilename=".cpp"><codeline><highlightclass="comment">//<sp/>traverse<sp/>all<sp/>successors<sp/>of<sp/>my_task</highlight><highlightclass="normal"></highlight></codeline>
<para>A task lives with its graph and belongs to only a graph at a time, and is not destroyed until the graph gets cleaned up. The lifetime of a task refers to the user-given callable object, including captured values. As long as the graph is alive, all the associated tasks exist.</para><para><simplesectkind="attention"><para>It is your responsibility to keep tasks and graph alive during their execution.</para></simplesect>
</para></sect1>
<sect1id="StaticTasking_1MoveATaskflow">
<title>Move a Taskflow</title>
<para>You can construct or assign a taskflow from a <emphasis>moved</emphasis> taskflow. Moving a taskflow to another will result in transferring the underlying graph data structures from one to the other.</para><para><programlistingfilename=".cpp"><codeline><highlightclass="normal"><refrefid="classtf_1_1Taskflow"kindref="compound">tf::Taskflow</ref><sp/>taskflow1,<sp/>taskflow3;</highlight></codeline>
</programlisting></para><para>You can only move a taskflow to another while that taskflow is not being run by an executor. Moving a running taskflow can result in undefined behavior. Please see <refrefid="ExecuteTaskflow_1ExecuteATaskflowWithTransferredOwnership"kindref="member">Execute a Taskflow with Transferred Ownership</ref> for more details. </para></sect1>