-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathDependentAsyncTasking.html
More file actions
333 lines (331 loc) · 38 KB
/
Copy pathDependentAsyncTasking.html
File metadata and controls
333 lines (331 loc) · 38 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
<!-- HTML header for doxygen 1.13.1-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=11"/>
<meta name="generator" content="Doxygen 1.13.1"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Taskflow: A General-purpose Task-parallel Programming System: Asynchronous Tasking with Dependencies</title>
<link href="tabs.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="jquery.js"></script>
<script type="text/javascript" src="dynsections.js"></script>
<script type="text/javascript" src="clipboard.js"></script>
<link href="navtree.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="navtreedata.js"></script>
<script type="text/javascript" src="navtree.js"></script>
<script type="text/javascript" src="resize.js"></script>
<script type="text/javascript" src="cookie.js"></script>
<link href="search/search.css" rel="stylesheet" type="text/css"/>
<script type="text/javascript" src="search/searchdata.js"></script>
<script type="text/javascript" src="search/search.js"></script>
<link href="doxygen.css" rel="stylesheet" type="text/css" />
<link href="custom.css" rel="stylesheet" type="text/css"/>
</head>
<body>
<div id="top"><!-- do not remove this div, it is closed by doxygen! -->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr id="projectrow">
<td id="projectlogo"><img alt="Logo" src="taskflow_logo.png"/></td>
<td id="projectalign">
<div id="projectname"><a href="https://github.com/taskflow/taskflow" style="color:inherit; text-decoration:none;">Taskflow: A General-purpose Task-parallel Programming System</a>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<!-- end header part -->
<!-- Generated by Doxygen 1.13.1 -->
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */
var searchBox = new SearchBox("searchBox", "search/",'.html');
/* @license-end */
</script>
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */
$(function() { codefold.init(0); });
/* @license-end */
</script>
<script type="text/javascript" src="menudata.js"></script>
<script type="text/javascript" src="menu.js"></script>
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */
$(function() {
initMenu('',true,false,'search.php','Search',true);
$(function() { init_search(); });
});
/* @license-end */
</script>
<div id="main-nav"></div>
</div><!-- top -->
<div id="side-nav" class="ui-resizable side-nav-resizable">
<div id="nav-tree">
<div id="nav-tree-contents">
<div id="nav-sync" class="sync"></div>
</div>
</div>
<div id="splitbar" style="-moz-user-select:none;"
class="ui-resizable-handle">
</div>
</div>
<script type="text/javascript">
/* @license magnet:?xt=urn:btih:d3d9a9a6595521f9666a5e94cc830dab83b65699&dn=expat.txt MIT */
$(function(){initNavTree('DependentAsyncTasking.html',''); initResizable(true); });
/* @license-end */
</script>
<div id="doc-content">
<!-- window showing the filter options -->
<div id="MSearchSelectWindow"
onmouseover="return searchBox.OnSearchSelectShow()"
onmouseout="return searchBox.OnSearchSelectHide()"
onkeydown="return searchBox.OnSearchSelectKey(event)">
</div>
<!-- iframe showing the search results (closed by default) -->
<div id="MSearchResultsWindow">
<div id="MSearchResults">
<div class="SRPage">
<div id="SRIndex">
<div id="SRResults"></div>
<div class="SRStatus" id="Loading">Loading...</div>
<div class="SRStatus" id="Searching">Searching...</div>
<div class="SRStatus" id="NoMatches">No Matches</div>
</div>
</div>
</div>
</div>
<div><div class="header">
<div class="headertitle"><div class="title">Asynchronous Tasking with Dependencies</div></div>
</div><!--header-->
<div class="contents">
<div class="toc"><h3>Table of Contents</h3>
<ul>
<li class="level1">
<a href="#WhenStaticTaskGraphsAreNotEnough">When Static Task Graphs Are Not Enough</a>
</li>
<li class="level1">
<a href="#CreateADynamicTaskGraph">Create a Dynamic Task Graph from an Executor</a>
</li>
<li class="level1">
<a href="#SpecifyARangeOfDependencies">Specify a Range of Dependencies</a>
</li>
<li class="level1">
<a href="#CreateADynamicTaskGraphFromARuntime">Create a Dynamic Task Graph from a Runtime</a>
</li>
<li class="level1">
<a href="#CreateADynamicTaskGraphByMultipleThreads">Create a Dynamic Task Graph from Multiple Threads</a>
</li>
<li class="level1">
<a href="#UnderstandTheLifetimeOfADependentAsyncTask">Understand the Lifetime of a Dependent-Async Task</a>
</li>
<li class="level1">
<a href="#QueryTheCompletionStatusOfDependentAsyncTasks">Query Completion Status with Cooperative Execution</a>
</li>
</ul>
</div>
<div class="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 <a class="el" href="AsyncTasking.html">Asynchronous Tasking</a> before this page.</p>
<h1><a class="anchor" id="WhenStaticTaskGraphsAreNotEnough"></a>
When Static Task Graphs Are Not Enough</h1>
<p>The standard Taskflow model is <em>construct-then-run</em>: you build the entire task graph upfront with <a class="el" href="classtf_1_1Taskflow.html" title="class to create a taskflow object">tf::Taskflow</a>, then hand it to <a class="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>
<dl class="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 class="fragment"><div class="line"><span class="keyword">auto</span> G1 = build_task_graph_1();</div>
<div class="line"><span class="keyword">auto</span> G2 = build_task_graph_2();</div>
<div class="line"> </div>
<div class="line"><span class="keywordflow">if</span>(G1.num_tasks() == 100) {</div>
<div class="line"> <span class="comment">// simple case: G2 runs after G1</span></div>
<div class="line"> G1.precede(G2);</div>
<div class="line">}</div>
<div class="line"><span class="keywordflow">else</span> {</div>
<div class="line"> <span class="comment">// complex case: G3 runs alongside G2, both after G1</span></div>
<div class="line"> <span class="keyword">auto</span> G3 = build_task_graph_3();</div>
<div class="line"> G1.precede(G2, G3);</div>
<div class="line"> </div>
<div class="line"> <span class="keywordflow">if</span>(G2.num_dependencies() >= 10) {</div>
<div class="line"> <span class="comment">// G2 is heavily connected — funnel into a single post-processing step</span></div>
<div class="line"> <span class="keyword">auto</span> G4 = build_task_graph_4();</div>
<div class="line"> G2.precede(G4);</div>
<div class="line"> G3.precede(G4);</div>
<div class="line"> }</div>
<div class="line"> <span class="keywordflow">else</span> {</div>
<div class="line"> <span class="comment">// G2 is lightly connected — fan out into two independent steps</span></div>
<div class="line"> <span class="keyword">auto</span> G5 = build_task_graph_5();</div>
<div class="line"> <span class="keyword">auto</span> G6 = build_task_graph_6();</div>
<div class="line"> G3.precede(G5, G6);</div>
<div class="line"> }</div>
<div class="line">}</div>
</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>
<dl class="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>
<div class="image">
<object type="image/svg+xml" data="dependent_async_execution_diagram.svg" style="pointer-events: none;"></object>
</div>
<p>Taskflow's dependent-async API, <a class="el" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> and <a class="el" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65" 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>
<h1><a class="anchor" id="CreateADynamicTaskGraph"></a>
Create a Dynamic Task Graph from an Executor</h1>
<p><a class="el" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::silent_dependent_async</a> and <a class="el" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> create a dependent-async task of type <a class="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. <a class="el" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> additionally returns a <a href="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>
<div class="dotgraph">
<iframe scrolling="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>
<div class="fragment"><div class="line"><a class="code hl_class" href="classtf_1_1Executor.html">tf::Executor</a> executor;</div>
<div class="line"> </div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"A\n"</span>); });</div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> B = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"B\n"</span>); }, A);</div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> C = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"C\n"</span>); }, A);</div>
<div class="line"><span class="keyword">auto</span> [D, fuD] = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3">dependent_async</a>([](){ printf(<span class="stringliteral">"D\n"</span>); }, B, C);</div>
<div class="line"> </div>
<div class="line">fuD.get(); <span class="comment">// waiting for D implies A, B, C have all finished</span></div>
<div class="ttc" id="aclasstf_1_1AsyncTask_html"><div class="ttname"><a href="classtf_1_1AsyncTask.html">tf::AsyncTask</a></div><div class="ttdoc">class to hold a dependent asynchronous task with shared ownership</div><div class="ttdef"><b>Definition</b> async_task.hpp:45</div></div>
<div class="ttc" id="aclasstf_1_1Executor_html"><div class="ttname"><a href="classtf_1_1Executor.html">tf::Executor</a></div><div class="ttdoc">class to create an executor</div><div class="ttdef"><b>Definition</b> executor.hpp:62</div></div>
<div class="ttc" id="aclasstf_1_1Executor_html_a0af0918b7179f9e42945fb407e0bad65"><div class="ttname"><a href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">tf::Executor::silent_dependent_async</a></div><div class="ttdeci">tf::AsyncTask silent_dependent_async(F &&func, Tasks &&... tasks)</div><div class="ttdoc">runs the given function asynchronously when the given predecessors finish</div></div>
<div class="ttc" id="aclasstf_1_1Executor_html_a3278e2611e43b80b65ef10a3391ddcc3"><div class="ttname"><a href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3">tf::Executor::dependent_async</a></div><div class="ttdeci">auto dependent_async(F &&func, Tasks &&... tasks)</div><div class="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>
<div class="fragment"><div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"A\n"</span>); });</div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> C = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"C\n"</span>); }, A);</div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> B = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"B\n"</span>); }, A);</div>
<div class="line"><span class="keyword">auto</span> [D, fuD] = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3">dependent_async</a>([](){ printf(<span class="stringliteral">"D\n"</span>); }, B, C);</div>
<div class="line">fuD.get();</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 <a class="el" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85" title="waits for all tasks to complete">tf::Executor::wait_for_all</a>:</p>
<div class="fragment"><div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"A\n"</span>); });</div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> B = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"B\n"</span>); }, A);</div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> C = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"C\n"</span>); }, A);</div>
<div class="line">tf::AsyncTask D = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){ printf(<span class="stringliteral">"D\n"</span>); }, B, C);</div>
<div class="line">executor.<a class="code hl_function" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85">wait_for_all</a>();</div>
<div class="ttc" id="aclasstf_1_1Executor_html_ab9aa252f70e9a40020a1e5a89d485b85"><div class="ttname"><a href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85">tf::Executor::wait_for_all</a></div><div class="ttdeci">void wait_for_all()</div><div class="ttdoc">waits for all tasks to complete</div></div>
</div><!-- fragment --><h1><a class="anchor" id="SpecifyARangeOfDependencies"></a>
Specify a Range of Dependencies</h1>
<p>Both <a class="el" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> and <a class="el" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65" 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><a class="el" href="classtf_1_1Executor.html#ac9fc4d707cd28b68b17fa70afb825e9c" 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><a class="el" href="classtf_1_1Executor.html#a91c8588046efef802c9c58823920290a" 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 <a class="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 class="fragment"><div class="line"><a class="code hl_class" href="classtf_1_1Executor.html">tf::Executor</a> executor;</div>
<div class="line">std::vector<tf::AsyncTask> predecessors;</div>
<div class="line"> </div>
<div class="line"><span class="keywordflow">for</span>(<span class="keywordtype">size_t</span> i = 0; i < N; i++) {</div>
<div class="line"> predecessors.push_back(executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){}));</div>
<div class="line">}</div>
<div class="line"> </div>
<div class="line"><span class="comment">// this task runs after all N predecessors have completed</span></div>
<div class="line">executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){},</div>
<div class="line"> predecessors.begin(), predecessors.end()</div>
<div class="line">);</div>
<div class="line"> </div>
<div class="line">executor.<a class="code hl_function" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85">wait_for_all</a>();</div>
</div><!-- fragment --><h1><a class="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 <a class="el" href="classtf_1_1Runtime.html" title="class to create a runtime task">tf::Runtime</a> object, using <a class="el" href="classtf_1_1Runtime.html#adf4363e398c883a421f4553445be2675" title="runs the given function asynchronously when the given predecessors finish">tf::Runtime::dependent_async</a> and <a class="el" href="classtf_1_1Runtime.html#a03e2912dc6a2b4763afd3f5e0cd38f0e" 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. <a class="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>
<div class="fragment"><div class="line"><a class="code hl_class" href="classtf_1_1Taskflow.html">tf::Taskflow</a> taskflow;</div>
<div class="line"><a class="code hl_class" href="classtf_1_1Executor.html">tf::Executor</a> executor;</div>
<div class="line">std::atomic<int> counter{0};</div>
<div class="line"> </div>
<div class="line"><a class="code hl_class" href="classtf_1_1Task.html">tf::Task</a> A = taskflow.<a class="code hl_function" href="classtf_1_1FlowBuilder.html#a4d52a7fe2814b264846a2085e931652c">emplace</a>([&](<a class="code hl_class" href="classtf_1_1Runtime.html">tf::Runtime</a>& rt) {</div>
<div class="line"> <span class="comment">// dynamically build a diamond sub-graph inside A</span></div>
<div class="line"> <a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> a = rt.<a class="code hl_function" href="classtf_1_1Runtime.html#a03e2912dc6a2b4763afd3f5e0cd38f0e">silent_dependent_async</a>([&](){ ++counter; });</div>
<div class="line"> <a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> b = rt.<a class="code hl_function" href="classtf_1_1Runtime.html#a03e2912dc6a2b4763afd3f5e0cd38f0e">silent_dependent_async</a>([&](){ ++counter; }, a);</div>
<div class="line"> tf::AsyncTask c = rt.<a class="code hl_function" href="classtf_1_1Runtime.html#a03e2912dc6a2b4763afd3f5e0cd38f0e">silent_dependent_async</a>([&](){ ++counter; }, a);</div>
<div class="line"> rt.<a class="code hl_function" href="classtf_1_1Runtime.html#a03e2912dc6a2b4763afd3f5e0cd38f0e">silent_dependent_async</a>([&](){ ++counter; }, b, c);</div>
<div class="line"> <span class="comment">// implicit join: all four sub-tasks finish before A completes</span></div>
<div class="line">});</div>
<div class="line"> </div>
<div class="line">tf::Task B = taskflow.<a class="code hl_function" href="classtf_1_1FlowBuilder.html#a4d52a7fe2814b264846a2085e931652c">emplace</a>([&]() {</div>
<div class="line"> assert(counter == 4); <span class="comment">// guaranteed: A's sub-graph has fully completed</span></div>
<div class="line">});</div>
<div class="line"> </div>
<div class="line">A.<a class="code hl_function" href="classtf_1_1Task.html#a8c78c453295a553c1c016e4062da8588">precede</a>(B);</div>
<div class="line">executor.<a class="code hl_function" href="classtf_1_1Executor.html#a519777f5783981d534e9e53b99712069">run</a>(taskflow).wait();</div>
<div class="ttc" id="aclasstf_1_1Executor_html_a519777f5783981d534e9e53b99712069"><div class="ttname"><a href="classtf_1_1Executor.html#a519777f5783981d534e9e53b99712069">tf::Executor::run</a></div><div class="ttdeci">tf::Future< void > run(Taskflow &taskflow)</div><div class="ttdoc">runs a taskflow once</div></div>
<div class="ttc" id="aclasstf_1_1FlowBuilder_html_a4d52a7fe2814b264846a2085e931652c"><div class="ttname"><a href="classtf_1_1FlowBuilder.html#a4d52a7fe2814b264846a2085e931652c">tf::FlowBuilder::emplace</a></div><div class="ttdeci">Task emplace(C &&callable)</div><div class="ttdoc">creates a static task</div><div class="ttdef"><b>Definition</b> flow_builder.hpp:1571</div></div>
<div class="ttc" id="aclasstf_1_1Runtime_html"><div class="ttname"><a href="classtf_1_1Runtime.html">tf::Runtime</a></div><div class="ttdoc">class to create a runtime task</div><div class="ttdef"><b>Definition</b> runtime.hpp:47</div></div>
<div class="ttc" id="aclasstf_1_1Runtime_html_a03e2912dc6a2b4763afd3f5e0cd38f0e"><div class="ttname"><a href="classtf_1_1Runtime.html#a03e2912dc6a2b4763afd3f5e0cd38f0e">tf::Runtime::silent_dependent_async</a></div><div class="ttdeci">tf::AsyncTask silent_dependent_async(F &&func, Tasks &&... tasks)</div><div class="ttdoc">runs the given function asynchronously when the given predecessors finish</div><div class="ttdef"><b>Definition</b> runtime.hpp:710</div></div>
<div class="ttc" id="aclasstf_1_1Task_html"><div class="ttname"><a href="classtf_1_1Task.html">tf::Task</a></div><div class="ttdoc">class to create a task handle over a taskflow node</div><div class="ttdef"><b>Definition</b> task.hpp:569</div></div>
<div class="ttc" id="aclasstf_1_1Task_html_a8c78c453295a553c1c016e4062da8588"><div class="ttname"><a href="classtf_1_1Task.html#a8c78c453295a553c1c016e4062da8588">tf::Task::precede</a></div><div class="ttdeci">Task & precede(Ts &&... tasks)</div><div class="ttdoc">adds precedence links from this to other tasks</div><div class="ttdef"><b>Definition</b> task.hpp:1258</div></div>
<div class="ttc" id="aclasstf_1_1Taskflow_html"><div class="ttname"><a href="classtf_1_1Taskflow.html">tf::Taskflow</a></div><div class="ttdoc">class to create a taskflow object</div><div class="ttdef"><b>Definition</b> taskflow.hpp:64</div></div>
</div><!-- fragment --><dl class="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 <a class="el" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85" title="waits for all tasks to complete">tf::Executor::wait_for_all</a>.</dd></dl>
<h1><a class="anchor" id="CreateADynamicTaskGraphByMultipleThreads"></a>
Create a Dynamic Task Graph from Multiple Threads</h1>
<p>Since <a class="el" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3" title="runs the given function asynchronously when the given predecessors finish">tf::Executor::dependent_async</a> and <a class="el" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65" 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 class="fragment"><div class="line"><a class="code hl_class" href="classtf_1_1Executor.html">tf::Executor</a> executor;</div>
<div class="line"> </div>
<div class="line"><span class="comment">// main thread creates task A</span></div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){});</div>
<div class="line"> </div>
<div class="line"><span class="comment">// two threads each add a dependent task</span></div>
<div class="line">std::thread t1([&](){</div>
<div class="line"> <a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> B = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){}, A);</div>
<div class="line">});</div>
<div class="line">std::thread t2([&](){</div>
<div class="line"> tf::AsyncTask C = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){}, A);</div>
<div class="line">});</div>
<div class="line"> </div>
<div class="line">executor.<a class="code hl_function" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85">wait_for_all</a>();</div>
<div class="line">t1.join();</div>
<div class="line">t2.join();</div>
</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>
<h1><a class="anchor" id="UnderstandTheLifetimeOfADependentAsyncTask"></a>
Understand the Lifetime of a Dependent-Async Task</h1>
<p><a class="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 <a href="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>
<div class="fragment"><div class="line"><span class="comment">// main thread retains shared ownership of A</span></div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> A = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){});</div>
<div class="line">assert(A.<a class="code hl_function" href="classtf_1_1AsyncTask.html#a6a4a54030f57d1ef05c04ae01825165d">use_count</a>() >= 1);</div>
<div class="line"> </div>
<div class="line"><span class="comment">// A remains alive while being registered as a predecessor of B</span></div>
<div class="line"><a class="code hl_class" href="classtf_1_1AsyncTask.html">tf::AsyncTask</a> B = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([](){}, A);</div>
<div class="line">assert(B.<a class="code hl_function" href="classtf_1_1AsyncTask.html#a6a4a54030f57d1ef05c04ae01825165d">use_count</a>() >= 1);</div>
<div class="ttc" id="aclasstf_1_1AsyncTask_html_a6a4a54030f57d1ef05c04ae01825165d"><div class="ttname"><a href="classtf_1_1AsyncTask.html#a6a4a54030f57d1ef05c04ae01825165d">tf::AsyncTask::use_count</a></div><div class="ttdeci">size_t use_count() const</div><div class="ttdoc">returns the number of shared owners that are currently managing this dependent-async task</div><div class="ttdef"><b>Definition</b> async_task.hpp:284</div></div>
</div><!-- fragment --><p><a class="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>
<h1><a class="anchor" id="QueryTheCompletionStatusOfDependentAsyncTasks"></a>
Query Completion Status with Cooperative Execution</h1>
<p><a class="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>
<div class="fragment"><div class="line"><a class="code hl_class" href="classtf_1_1Executor.html">tf::Executor</a> executor;</div>
<div class="line"> </div>
<div class="line"><span class="comment">// stage 1: load and parse data</span></div>
<div class="line"><span class="keyword">auto</span> [parse, fu_parse] = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3">dependent_async</a>([]() -> <span class="keywordtype">int</span> {</div>
<div class="line"> <span class="keywordflow">return</span> load_and_parse(); <span class="comment">// returns the number of records parsed</span></div>
<div class="line">});</div>
<div class="line"> </div>
<div class="line"><span class="comment">// stage 2: validate records — depends on parse</span></div>
<div class="line"><span class="keyword">auto</span> [validate, fu_validate] = executor.<a class="code hl_function" href="classtf_1_1Executor.html#a3278e2611e43b80b65ef10a3391ddcc3">dependent_async</a>([&]() -> <span class="keywordtype">bool</span> {</div>
<div class="line"> <span class="keywordflow">return</span> validate_records();</div>
<div class="line">}, parse);</div>
<div class="line"> </div>
<div class="line"><span class="comment">// the main thread keeps the executor's workers alive (work-stealing loop)</span></div>
<div class="line"><span class="comment">// while waiting for both stages to complete</span></div>
<div class="line">executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0fc6eb19f168dc4a9cd0a7c6187c1d2d">corun_until</a>([&](){</div>
<div class="line"> <span class="keywordflow">return</span> parse.is_done() && validate.is_done();</div>
<div class="line">});</div>
<div class="line"> </div>
<div class="line"><span class="comment">// now safe to inspect results and decide on the next step</span></div>
<div class="line"><span class="keywordtype">int</span> n_records = fu_parse.get();</div>
<div class="line"><span class="keywordtype">bool</span> valid = fu_validate.get();</div>
<div class="line"> </div>
<div class="line"><span class="keywordflow">if</span>(valid) {</div>
<div class="line"> <span class="comment">// submit stage 3 only if validation passed</span></div>
<div class="line"> executor.<a class="code hl_function" href="classtf_1_1Executor.html#a0af0918b7179f9e42945fb407e0bad65">silent_dependent_async</a>([=]() {</div>
<div class="line"> process_records(n_records);</div>
<div class="line"> }, validate);</div>
<div class="line">}</div>
<div class="line"> </div>
<div class="line">executor.<a class="code hl_function" href="classtf_1_1Executor.html#ab9aa252f70e9a40020a1e5a89d485b85">wait_for_all</a>();</div>
<div class="ttc" id="aclasstf_1_1Executor_html_a0fc6eb19f168dc4a9cd0a7c6187c1d2d"><div class="ttname"><a href="classtf_1_1Executor.html#a0fc6eb19f168dc4a9cd0a7c6187c1d2d">tf::Executor::corun_until</a></div><div class="ttdeci">void corun_until(P &&predicate)</div><div class="ttdoc">keeps running the work-stealing loop until the predicate returns true</div></div>
</div><!-- fragment --><dl class="section note"><dt>Note</dt><dd><a class="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 <a class="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 <a class="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 -->
<div id="nav-path" class="navpath"><!-- id is needed for treeview function! -->
<ul>
<li class="navelem"><a class="el" href="Cookbook.html">Cookbook</a></li>
<li class="footer">
Maintained by <a href="https://tsung-wei-huang.github.io/">Dr. Tsung-Wei Huang</a>
—
Generated by <a href="https://www.doxygen.org/index.html"><img class="footer" src="doxygen.svg" width="104" height="31" alt="doxygen"/></a> 1.13.1
</li>
</ul>
</div>