<divclass="headertitle"><divclass="title">Asynchronous Tasking with Dependencies</div></div>
</div><!--header-->
<divclass="contents">
<divclass="toc"><h3>Table of Contents</h3>
<ul>
<liclass="level1">
<ahref="#WhenStaticTaskGraphsAreNotEnough">When Static Task Graphs Are Not Enough</a>
</li>
<liclass="level1">
<ahref="#CreateADynamicTaskGraph">Create a Dynamic Task Graph from an Executor</a>
</li>
<liclass="level1">
<ahref="#SpecifyARangeOfDependencies">Specify a Range of Dependencies</a>
</li>
<liclass="level1">
<ahref="#CreateADynamicTaskGraphFromARuntime">Create a Dynamic Task Graph from a Runtime</a>
</li>
<liclass="level1">
<ahref="#CreateADynamicTaskGraphByMultipleThreads">Create a Dynamic Task Graph from Multiple Threads</a>
</li>
<liclass="level1">
<ahref="#UnderstandTheLifetimeOfADependentAsyncTask">Understand the Lifetime of a Dependent-Async Task</a>
</li>
<liclass="level1">
<ahref="#QueryTheCompletionStatusOfDependentAsyncTasks">Query Completion Status with Cooperative Execution</a>
</li>
</ul>
</div>
<divclass="textblock"><p>Taskflow supports creating task graphs dynamically using dependent async tasks so you can handle more challenging parallel problems in a dynamic environment. This type of task graph construction is referred to as <em>dynamic task graph programming</em> (DTGP). We recommend reading <aclass="el" href="AsyncTasking.html">Asynchronous Tasking</a> before this page.</p>
<p>The standard Taskflow model is <em>construct-then-run</em>: you build the entire task graph upfront with <aclass="el" href="classtf_1_1Taskflow.html" title="class to create a taskflow object">tf::Taskflow</a>, then hand it to <aclass="el" href="classtf_1_1Executor.html" title="class to create an executor">tf::Executor</a> to execute. This model is also referred to as <em>static task graph programming</em> (STGP), which is clean, predictable, and efficient for workloads whose structure is known before execution begins. However, two scenarios of problems cannot be handled well by STGP, explained below:</p>
<dlclass="section user"><dt>Scenario A: Graph topology determined at runtime</dt><dd></dd></dl>
<p>Consider a workflow where the structure of the task graph — how many sub-graphs exist, which ones run in parallel, which depend on which — is decided entirely by runtime conditions and properties of the graphs themselves:</p>
</div><!-- fragment --><p>Building this statically would require enumerating every possible branch as a separate pre-built taskflow and selecting one at program start. That approach is brittle, wasteful, and breaks down completely when the branching logic depends on properties of the graphs themselves — as shown here, where the structure of <code>G1</code> and <code>G2</code> determines what runs next. Dynamic task graph programming solves this directly: sub-graphs are created and wired as control flow unfolds, so the final graph matches the actual execution path exactly.</p>
<dlclass="section user"><dt>Scenario B — Hiding graph construction latency</dt><dd></dd></dl>
<p>In large graphs, constructing every task node can itself take non-trivial time — allocating buffers, loading metadata, resolving file paths. With construct-then-run, all of that setup must complete before a single task begins executing. With dynamic task graph programming, a task can begin executing the moment its dependencies are satisfied, even while downstream tasks are still being constructed. This <em>overlap</em> between graph creation and task execution can significantly reduce end-to-end latency.</p>
<p>The figure below illustrates this difference on a four-task graph. In the static model, the entire taskflow is constructed before any task runs. In the dynamic model, execution of early tasks overlaps with the construction of later tasks:</p>
<p>Taskflow's dependent-async API, <aclass="el" href="classtf_1_1Executor.html#a0015352aa28b50251a970354a0c4a159" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> and <aclass="el" href="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::silent_dependent_async</a>, is designed precisely for these scenarios. Each task is submitted individually with an explicit list of predecessor tasks, and the executor begins running it as soon as all predecessors complete, without waiting for the rest of the graph to be defined.</p>
<p><aclass="el" href="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::silent_dependent_async</a> and <aclass="el" href="classtf_1_1Executor.html#a0015352aa28b50251a970354a0c4a159" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> create a dependent-async task of type <aclass="el" href="classtf_1_1AsyncTask.html" title="class to hold a dependent asynchronous task with shared ownership">tf::AsyncTask</a> and schedule it for execution as soon as its dependencies are satisfied. <aclass="el" href="classtf_1_1Executor.html#a0015352aa28b50251a970354a0c4a159" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> additionally returns a <ahref="https://en.cppreference.com/w/cpp/thread/future">std::future</a> that eventually holds the result of the callable.</p>
<p>The example below dynamically creates the following diamond task graph, where <code>A</code> runs first, <code>B</code> and <code>C</code> run in parallel after <code>A</code>, and <code>D</code> runs after both <code>B</code> and <code>C:</code></p>
<divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_simple.svg" width="323" height="131"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<divclass="line">fuD.get(); <spanclass="comment">// waiting for D implies A, B, C have all finished</span></div>
<divclass="ttc" id="aclasstf_1_1AsyncTask_html"><divclass="ttname"><ahref="classtf_1_1AsyncTask.html">tf::AsyncTask</a></div><divclass="ttdoc">class to hold a dependent asynchronous task with shared ownership</div><divclass="ttdef"><b>Definition</b> async_task.hpp:45</div></div>
<divclass="ttc" id="aclasstf_1_1Executor_html"><divclass="ttname"><ahref="classtf_1_1Executor.html">tf::Executor</a></div><divclass="ttdoc">class to create an executor</div><divclass="ttdef"><b>Definition</b> executor.hpp:62</div></div>
<divclass="ttc" id="aclasstf_1_1Executor_html_a0015352aa28b50251a970354a0c4a159"><divclass="ttname"><ahref="classtf_1_1Executor.html#a0015352aa28b50251a970354a0c4a159">tf::Executor::dependent_async</a></div><divclass="ttdeci">auto dependent_async(F &&func, Tasks &&... tasks)</div><divclass="ttdoc">runs the given function asynchronously when the given predecessors finish</div></div>
<divclass="ttc" id="aclasstf_1_1Executor_html_a09e04696a50841c118472b2c2a7ff6f5"><divclass="ttname"><ahref="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5">tf::Executor::silent_dependent_async</a></div><divclass="ttdeci">tf::AsyncTask silent_dependent_async(F &&func, Tasks &&... tasks)</div><divclass="ttdoc">runs the given function asynchronously when the given predecessors finish</div></div>
</div><!-- fragment --><p>Because task execution begins as soon as dependencies are met, this model requires you to express tasks in a valid <em>topological</em> order — you can only name a task as a predecessor after it has already been created. For the diamond above there are two valid orderings; the alternative is:</p>
<divclass="fragment"><divclass="line"><aclass="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<aclass="code hl_function" href="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5">silent_dependent_async</a>([](){ printf(<spanclass="stringliteral">"A\n"</span>); });</div>
</div><!-- fragment --><p>In addition to synchronising on a specific task via its future, you can wait for all outstanding dependent-async tasks using <aclass="el" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85" title="waits for all tasks to complete">tf::Executor::wait_for_all</a>:</p>
<divclass="fragment"><divclass="line"><aclass="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<aclass="code hl_function" href="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5">silent_dependent_async</a>([](){ printf(<spanclass="stringliteral">"A\n"</span>); });</div>
<divclass="ttc" id="aclasstf_1_1Executor_html_ab9aa252f70e9a40020a1e5a89d485b85"><divclass="ttname"><ahref="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85">tf::Executor::wait_for_all</a></div><divclass="ttdeci">void wait_for_all()</div><divclass="ttdoc">waits for all tasks to complete</div></div>
</div><!-- fragment --><h1><aclass="anchor" id="SpecifyARangeOfDependencies"></a>
Specify a Range of Dependencies</h1>
<p>Both <aclass="el" href="classtf_1_1Executor.html#a0015352aa28b50251a970354a0c4a159" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> and <aclass="el" href="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::silent_dependent_async</a> accept an arbitrary number of predecessor tasks as variadic arguments. When the number of predecessors is not known until runtime — for example, when it depends on the size of a data set — you can use the iterator overloads that accept a range <code>[first, last)</code>:</p>
<ul>
<li><aclass="el" href="classtf_1_1Executor.html#ad02f305d1806309091327baa982364f1" title="runs the given function asynchronously when the given range of predecessors finish">tf::Executor::dependent_async(F&& func, I first, I last)</a></li>
<li><aclass="el" href="classtf_1_1Executor.html#a86a5f8cd4427df6285b03945120c9ab5" title="runs the given function asynchronously when the given range of predecessors finish">tf::Executor::silent_dependent_async(F&& func, I first, I last)</a></li>
</ul>
<p>The iterator's dereferenced type must be convertible to <aclass="el" href="classtf_1_1AsyncTask.html" title="class to hold a dependent asynchronous task with shared ownership">tf::AsyncTask</a>. The example below creates a final task that depends on <code>N</code> previously created tasks, where <code>N</code> is a runtime variable:</p>
</div><!-- fragment --><h1><aclass="anchor" id="CreateADynamicTaskGraphFromARuntime"></a>
Create a Dynamic Task Graph from a Runtime</h1>
<p>You can also create dependent-async tasks from within a running task that has access to a <aclass="el" href="classtf_1_1Runtime.html" title="class to create a runtime task">tf::Runtime</a> object, using <aclass="el" href="classtf_1_1Runtime.html#ae07fee37c60c59abe07993a7edb94624" title="runs the given function asynchronously when the given predecessors finish">tf::Runtime::dependent_async</a> and <aclass="el" href="classtf_1_1Runtime.html#a65c30be3a899dcd1ba16ad50ebb8de0d" title="runs the given function asynchronously when the given predecessors finish">tf::Runtime::silent_dependent_async</a>. The API mirrors the executor-level interface, but with one important distinction: all dependent-async tasks spawned from a runtime are <em>parented</em> to that runtime and are <em>implicitly</em> joined at the end of its scope. This means the runtime task does not complete — and control does not pass to the next task in the graph — until every dependent-async task it spawned has finished. This property is especially useful for implementing dynamic sub-graphs inside a larger static graph: a single runtime task can build and run an entire dynamic task graph as part of one logical step, with the surrounding graph remaining unaware of the internal structure.</p>
<p>The example below shows a static graph where task <code>A</code> dynamically builds a diamond sub-graph at runtime. <aclass="el" href="classtf_1_1Task.html" title="class to create a task handle over a taskflow node">Task</a><code>B</code> is guaranteed to see the results of the entire sub-graph because the implicit join ensures all sub-tasks finish before <code>A</code> completes:</p>
<divclass="ttc" id="aclasstf_1_1Executor_html_a519777f5783981d534e9e53b99712069"><divclass="ttname"><ahref="classtf_1_1Executor.html#a519777f5783981d534e9e53b99712069">tf::Executor::run</a></div><divclass="ttdeci">tf::Future< void > run(Taskflow &taskflow)</div><divclass="ttdoc">runs a taskflow once</div></div>
<divclass="ttc" id="aclasstf_1_1FlowBuilder_html_a4d52a7fe2814b264846a2085e931652c"><divclass="ttname"><ahref="classtf_1_1FlowBuilder.html#a4d52a7fe2814b264846a2085e931652c">tf::FlowBuilder::emplace</a></div><divclass="ttdeci">Task emplace(C &&callable)</div><divclass="ttdoc">creates a static task</div><divclass="ttdef"><b>Definition</b> flow_builder.hpp:1781</div></div>
<divclass="ttc" id="aclasstf_1_1Runtime_html"><divclass="ttname"><ahref="classtf_1_1Runtime.html">tf::Runtime</a></div><divclass="ttdoc">class to create a runtime task</div><divclass="ttdef"><b>Definition</b> runtime.hpp:47</div></div>
<divclass="ttc" id="aclasstf_1_1Runtime_html_a65c30be3a899dcd1ba16ad50ebb8de0d"><divclass="ttname"><ahref="classtf_1_1Runtime.html#a65c30be3a899dcd1ba16ad50ebb8de0d">tf::Runtime::silent_dependent_async</a></div><divclass="ttdeci">tf::AsyncTask silent_dependent_async(F &&func, Tasks &&... tasks)</div><divclass="ttdoc">runs the given function asynchronously when the given predecessors finish</div><divclass="ttdef"><b>Definition</b> runtime.hpp:705</div></div>
<divclass="ttc" id="aclasstf_1_1Task_html"><divclass="ttname"><ahref="classtf_1_1Task.html">tf::Task</a></div><divclass="ttdoc">class to create a task handle over a taskflow node</div><divclass="ttdef"><b>Definition</b> task.hpp:569</div></div>
<divclass="ttc" id="aclasstf_1_1Task_html_a8c78c453295a553c1c016e4062da8588"><divclass="ttname"><ahref="classtf_1_1Task.html#a8c78c453295a553c1c016e4062da8588">tf::Task::precede</a></div><divclass="ttdeci">Task & precede(Ts &&... tasks)</div><divclass="ttdoc">adds precedence links from this to other tasks</div><divclass="ttdef"><b>Definition</b> task.hpp:1305</div></div>
<divclass="ttc" id="aclasstf_1_1Taskflow_html"><divclass="ttname"><ahref="classtf_1_1Taskflow.html">tf::Taskflow</a></div><divclass="ttdoc">class to create a taskflow object</div><divclass="ttdef"><b>Definition</b> taskflow.hpp:64</div></div>
</div><!-- fragment --><dlclass="section note"><dt>Note</dt><dd>Dependent-async tasks created from a runtime belong to that runtime and are automatically joined when the runtime goes out of scope. In contrast, dependent-async tasks created from an executor have no parent and must be explicitly synchronised via a future or <aclass="el" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85" title="waits for all tasks to complete">tf::Executor::wait_for_all</a>.</dd></dl>
Create a Dynamic Task Graph from Multiple Threads</h1>
<p>Since <aclass="el" href="classtf_1_1Executor.html#a0015352aa28b50251a970354a0c4a159" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> and <aclass="el" href="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::silent_dependent_async</a> are thread-safe, multiple threads can collaborate to build the same dynamic task graph concurrently, provided the overall topological order is respected. The example below uses three threads to build a graph where <code>B</code> and <code>C</code> both depend on <code>A:</code></p>
</div><!-- fragment --><p>Regardless of whether <code>t1</code> runs before or after <code>t2</code>, both orderings (<code>ABC</code> or <code>ACB</code>) satisfy the dependency that <code>B</code> and <code>C</code> follow <code>A</code>.</p>
Understand the Lifetime of a Dependent-Async Task</h1>
<p><aclass="el" href="classtf_1_1AsyncTask.html" title="class to hold a dependent asynchronous task with shared ownership">tf::AsyncTask</a> is a lightweight handle that holds <em>shared</em> ownership of the underlying task object. This shared ownership ensures the task remains alive when it is added to the dependency list of another task, preventing the <ahref="https://en.wikipedia.org/wiki/ABA_problem">ABA problem</a> that would arise if the task were destroyed before its dependents had been registered:</p>
<divclass="fragment"><divclass="line"><spanclass="comment">// main thread retains shared ownership of A</span></div>
<divclass="line"><aclass="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<aclass="code hl_function" href="classtf_1_1Executor.html#a09e04696a50841c118472b2c2a7ff6f5">silent_dependent_async</a>([](){});</div>
<divclass="ttc" id="aclasstf_1_1AsyncTask_html_a6a4a54030f57d1ef05c04ae01825165d"><divclass="ttname"><ahref="classtf_1_1AsyncTask.html#a6a4a54030f57d1ef05c04ae01825165d">tf::AsyncTask::use_count</a></div><divclass="ttdeci">size_t use_count() const</div><divclass="ttdoc">returns the number of shared owners that are currently managing this dependent-async task</div><divclass="ttdef"><b>Definition</b> async_task.hpp:284</div></div>
</div><!-- fragment --><p><aclass="el" href="classtf_1_1AsyncTask.html" title="class to hold a dependent asynchronous task with shared ownership">tf::AsyncTask</a> is implemented in a similar way to <code>std::shared_ptr</code> and is cheap to copy or move. When a worker finishes executing a dependent-async task, it removes the task from the executor, decrementing the shared owner count by one. The task is destroyed when that count reaches zero.</p>
Query Completion Status with Cooperative Execution</h1>
<p><aclass="el" href="classtf_1_1AsyncTask.html#aefeefa30d7cafdfbb7dc8def542e8e51" title="checks if this dependent-async task finishes">tf::AsyncTask::is_done</a> returns <code>true</code> once the task has finished executing its callable, and <code>false</code> before that point. This is useful when you need to check whether a specific task has completed before proceeding, without blocking the calling thread. Consider a scenario where a main thread submits a chain of data-processing tasks and needs to verify the results of an intermediate stage before deciding what to submit next:</p>
<divclass="ttc" id="aclasstf_1_1Executor_html_a0fc6eb19f168dc4a9cd0a7c6187c1d2d"><divclass="ttname"><ahref="classtf_1_1Executor.html#a0fc6eb19f168dc4a9cd0a7c6187c1d2d">tf::Executor::corun_until</a></div><divclass="ttdeci">void corun_until(P &&predicate)</div><divclass="ttdoc">keeps running the work-stealing loop until the predicate returns true</div></div>
</div><!-- fragment --><dlclass="section note"><dt>Note</dt><dd><aclass="el" href="classtf_1_1AsyncTask.html#aefeefa30d7cafdfbb7dc8def542e8e51" title="checks if this dependent-async task finishes">tf::AsyncTask::is_done</a> is designed to be used together with <aclass="el" href="classtf_1_1Executor.html#a0fc6eb19f168dc4a9cd0a7c6187c1d2d" title="keeps running the work-stealing loop until the predicate returns true">tf::Executor::corun_until</a>, which keeps the calling worker thread active in the work-stealing loop rather than blocking it. Blocking a worker thread with a spin-wait or <code>std::future::get</code> while inside the executor can cause deadlock if all workers are blocked waiting for tasks that cannot be scheduled. See <aclass="el" href="ExecuteTaskflow.html#ExecuteATaskflowFromAnInternalWorker">Execute a Taskflow from an Internal Worker Cooperatively</a> for more details. </dd></dl>
</div></div><!-- contents -->
</div><!-- PageDoc -->
</div><!-- doc-content -->
<!-- HTML footer for doxygen 1.13.1-->
<!-- start footer part -->
<divid="nav-path" class="navpath"><!-- id is needed for treeview function! -->