<ahref="#ExceptionHandlingRunningTaskflow">Catch an Exception from a Running Taskflow</a>
</li>
<liclass="level1">
<ahref="#ExceptionHandlingSubflow">Catch an Exception from a Subflow</a>
</li>
<liclass="level1">
<ahref="#ExceptionHandlingAsyncTask">Catch an Exception from an Async Task</a>
</li>
<liclass="level1">
<ahref="#ExceptionHandlingCorun">Catch an Exception from a Corun Loop</a>
</li>
<liclass="level1">
<ahref="#RetrieveTheExceptionPointerOfATask">Retrieve the Exception Pointer of a Task</a>
</li>
<liclass="level1">
<ahref="#DisableExceptionHandling">Disable Exception Handling at Compile Time</a>
</li>
</ul>
</div>
<divclass="textblock"><p>Taskflow provides first-class support for exception handling in parallel programs — a capability that most task-parallel libraries deliberately omit. This page explains how exceptions propagate through task graphs, subflows, async tasks, and corun loops, and how to retrieve or suppress them.</p>
<p>In a sequential program, exception handling is straightforward: an exception thrown in a function propagates up the call stack until it reaches a <code>catch</code> block or terminates the program.</p>
</div><!-- fragment --><p>However, in a parallel runtime, this mechanism breaks down entirely. Worker threads execute tasks independently of the application thread. When a task on worker thread 3 throws an exception, there is no call stack connection back to the <code>try</code> block the application wrote. The exception cannot simply propagate upward, as it will terminate the worker thread if left unhandled, corrupting the entire executor. A correct parallel runtime must therefore intercept every exception thrown inside a task, store it safely across thread boundaries, cancel the appropriate downstream tasks to avoid operating on invalid state, and re-raise the exception at the right point, either synchronously on a waiting thread or asynchronously via a future handle.</p>
<divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_exception_handling_hard.svg" width="863" height="419"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<p>Getting this right without sacrificing parallel efficiency is non-trivial, which is why most task-parallel libraries simply ignore the problem and leave exception safety entirely to the user. Taskflow handles it transparently.</p>
<p>When a task throws an exception, Taskflow immediately cancels the execution of its parent taskflow — all subsequent tasks that depend on the throwing task are skipped. The exception itself is then routed to the appropriate reporting context according to the following three scenarios.</p>
<divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_exception-handling-logic.svg" width="874" height="258"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<p>When multiple scenarios apply, Taskflow prioritises them in order: Scenario 1 first, then Scenario 2, then Scenario 3.</p>
<p>If the taskflow is executed in a blocking context — such as <aclass="el" href="classtf_1_1Executor.html#a8fcd9e0557922bb8194999f0cd433ea8" title="runs a target graph and waits until it completes using an internal worker of this executor">tf::Executor::corun</a> or <aclass="el" href="classtf_1_1Subflow.html#a59fcac1323e70d920088dd37bd0be245" title="enables the subflow to join its parent task">tf::Subflow::join</a> — the exception is immediately rethrown to the calling thread as soon as it is detected. The executor does not defer or aggregate it.</p>
</div><!-- fragment --><h2><aclass="anchor" id="Scenario2AsynchronousExceptionPropagation"></a>
Scenario 2: Asynchronous Propagation</h2>
<p>When tasks are launched via <aclass="el" href="classtf_1_1Executor.html#a519777f5783981d534e9e53b99712069" title="runs a taskflow once">tf::Executor::run</a> or <aclass="el" href="classtf_1_1Executor.html#af960048056f7c6b5bc71f4f526f05df7" title="creates a parameterized asynchronous task to run the given function">tf::Executor::async</a>, the exception is captured and stored in the shared state of the returned <aclass="el" href="classtf_1_1Future.html" title="class to access the result of an execution">tf::Future</a> or <code>std::future</code>. It is rethrown when the application calls <code>get()</code> on that future.</p>
<divclass="fragment"><divclass="line"><aclass="code hl_class" href="classtf_1_1Future.html">tf::Future<void></a> fu = executor.run(taskflow);</div>
<divclass="ttc" id="aclasstf_1_1Future_html"><divclass="ttname"><ahref="classtf_1_1Future.html">tf::Future</a></div><divclass="ttdoc">class to access the result of an execution</div><divclass="ttdef"><b>Definition</b> taskflow.hpp:640</div></div>
</div><!-- fragment --><h2><aclass="anchor" id="Scenario3ContextualExceptionPropagation"></a>
Scenario 3: Contextual Propagation</h2>
<p>This scenario is reached only when no blocking context and no observable shared state exist — for example, a task launched via <aclass="el" href="classtf_1_1Executor.html#a0461cb2c459c9f9473c72af06af9c701" title="similar to tf::Executor::async but does not return a future object">tf::Executor::silent_async</a>. In this case Taskflow first attempts to propagate the exception to the nearest parent execution context. If no such parent exists, the exception is silently suppressed and stored locally within the throwing task.</p>
<divclass="fragment"><divclass="line"><spanclass="comment">// No parent context: exception is silently suppressed</span></div>
<divclass="line"><spanclass="keywordflow">throw</span> std::runtime_error(<spanclass="stringliteral">"this exception is suppressed"</span>);</div>
<divclass="line">});</div>
<divclass="line"></div>
<divclass="line"><spanclass="comment">// Parent context exists: exception propagates to the task group</span></div>
<divclass="line"><spanclass="keywordflow">throw</span> std::runtime_error(<spanclass="stringliteral">"this propagates to the parent task group"</span>);</div>
<divclass="ttc" id="aclasstf_1_1TaskGroup_html"><divclass="ttname"><ahref="classtf_1_1TaskGroup.html">tf::TaskGroup</a></div><divclass="ttdoc">class to create a task group from a task</div><divclass="ttdef"><b>Definition</b> task_group.hpp:61</div></div>
<divclass="ttc" id="aclasstf_1_1TaskGroup_html_a1f481dc466e3107a08346d1a124677bc"><divclass="ttname"><ahref="classtf_1_1TaskGroup.html#a1f481dc466e3107a08346d1a124677bc">tf::TaskGroup::corun</a></div><divclass="ttdeci">void corun()</div><divclass="ttdoc">corun all tasks spawned by this task group with other workers</div><divclass="ttdef"><b>Definition</b> task_group.hpp:721</div></div>
<divclass="ttc" id="aclasstf_1_1TaskGroup_html_acf90acfcaf9468adc56bf647208a9e78"><divclass="ttname"><ahref="classtf_1_1TaskGroup.html#acf90acfcaf9468adc56bf647208a9e78">tf::TaskGroup::silent_async</a></div><divclass="ttdeci">void silent_async(F &&f)</div><divclass="ttdoc">runs the given function asynchronously without returning any future object</div><divclass="ttdef"><b>Definition</b> task_group.hpp:752</div></div>
</div><!-- fragment --><h2><aclass="anchor" id="ExceptionHandlingAlgorithmFlow"></a>
Algorithm Flow</h2>
<p>The figure below illustrates the full decision process. When a task throws, the runtime walks up the task hierarchy to mark the execution path as exceptional and identifies two kinds of anchor nodes: an <em>explicit</em><em>anchor</em> (a blocking context such as <code>corun</code> or <code>join</code>) and an <em>implicit</em><em>anchor</em> (a parent task group). If an explicit anchor is found, the exception is captured and rethrown on the waiting application thread (Scenario 1). If no explicit anchor exists but a shared state is associated, the exception is stored for asynchronous retrieval (Scenario 2). Otherwise the runtime propagates to the nearest implicit anchor if one exists, or suppresses the exception locally (Scenario 3). This algorithm ensures exceptions are reported whenever a reporting context is available while preserving safe and efficient parallel execution.</p>
<divclass="dotgraph">
<iframescrolling="no" frameborder="0" src="dot_exception-handling-algorithm.svg" width="838" height="707"><p><b>This browser is not able to show SVG: try Firefox, Chrome, Safari, or Opera instead.</b></p></iframe></div>
<p>The most common case is catching an exception thrown by a task inside a taskflow submitted with <aclass="el" href="classtf_1_1Executor.html#a519777f5783981d534e9e53b99712069" title="runs a taskflow once">tf::Executor::run</a>. The executor captures the exception and stores it in the shared state of the returned <aclass="el" href="classtf_1_1Future.html" title="class to access the result of an execution">tf::Future</a>. Calling <code>get()</code> rethrows it:</p>
<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_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_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><aclass="el" href="classtf_1_1Future.html" title="class to access the result of an execution">tf::Future</a> is derived from <ahref="https://en.cppreference.com/w/cpp/thread/future">std::future</a> and inherits all standard exception-handling behaviours defined by the C++ standard.</dd></dl>
<p>When a task throws, the executor immediately cancels the rest of the taskflow — all tasks that depend on the throwing task are skipped. The following example shows this with a two-task chain where <code>B</code> depends on <code>A:</code></p>
<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>
</div><!-- fragment --><divclass="fragment"><divclass="line">exception on A</div>
</div><!-- fragment --><p>When multiple tasks throw exceptions concurrently, Taskflow propagates exactly one to the <code>catch</code> block. The others are silently caught and stored within their respective tasks (see <aclass="el" href="#RetrieveTheExceptionPointerOfATask">Retrieve the Exception Pointer of a Task</a>). In the diamond taskflow below, both <code>B</code> and <code>C</code> may throw simultaneously — only one exception reaches the application:</p>
<divclass="line"> std::cerr << e.what() << <spanclass="charliteral">'\n'</span>; <spanclass="comment">// either B's or C's exception</span></div>
<divclass="line">}</div>
<divclass="ttc" id="aclasstf_1_1Task_html_a331b1b726555072e7c7d10941257f664"><divclass="ttname"><ahref="classtf_1_1Task.html#a331b1b726555072e7c7d10941257f664">tf::Task::succeed</a></div><divclass="ttdeci">Task & succeed(Ts &&... tasks)</div><divclass="ttdoc">adds precedence links from other tasks to this</div><divclass="ttdef"><b>Definition</b> task.hpp:1313</div></div>
</div><!-- fragment --><h1><aclass="anchor" id="ExceptionHandlingSubflow"></a>
Catch an Exception from a Subflow</h1>
<p>When you explicitly join a subflow with <aclass="el" href="classtf_1_1Subflow.html#a59fcac1323e70d920088dd37bd0be245" title="enables the subflow to join its parent task">tf::Subflow::join</a>, you can catch exceptions thrown by its child tasks at the join point:</p>
<divclass="ttc" id="aclasstf_1_1Subflow_html"><divclass="ttname"><ahref="classtf_1_1Subflow.html">tf::Subflow</a></div><divclass="ttdoc">class to construct a subflow graph from the execution of a dynamic task</div><divclass="ttdef"><b>Definition</b> flow_builder.hpp:1956</div></div>
<divclass="ttc" id="aclasstf_1_1Subflow_html_a59fcac1323e70d920088dd37bd0be245"><divclass="ttname"><ahref="classtf_1_1Subflow.html#a59fcac1323e70d920088dd37bd0be245">tf::Subflow::join</a></div><divclass="ttdeci">void join()</div><divclass="ttdoc">enables the subflow to join its parent task</div></div>
</div><!-- fragment --><divclass="fragment"><divclass="line">Task A</div>
<divclass="line">caught at join: exception on A</div>
</div><!-- fragment --><p>If you do not catch the exception at the join point, it propagates up to the parent taskflow and is rethrown when the application calls <code>get()</code>:</p>
</div><!-- fragment --><divclass="fragment"><divclass="line">Task A</div>
<divclass="line">caught at taskflow: exception on A</div>
</div><!-- fragment --><h1><aclass="anchor" id="ExceptionHandlingAsyncTask"></a>
Catch an Exception from an Async Task</h1>
<p><aclass="el" href="classtf_1_1Executor.html#af960048056f7c6b5bc71f4f526f05df7" title="creates a parameterized asynchronous task to run the given function">tf::Executor::async</a> behaves like <code>std::async:</code> the exception is stored in the returned <code>std::future</code> and rethrown on <code>get()</code>:</p>
<divclass="ttc" id="aclasstf_1_1Executor_html_af960048056f7c6b5bc71f4f526f05df7"><divclass="ttname"><ahref="classtf_1_1Executor.html#af960048056f7c6b5bc71f4f526f05df7">tf::Executor::async</a></div><divclass="ttdeci">auto async(P &&params, F &&func)</div><divclass="ttdoc">creates a parameterized asynchronous task to run the given function</div></div>
</div><!-- fragment --><p><aclass="el" href="classtf_1_1Executor.html#a0461cb2c459c9f9473c72af06af9c701" title="similar to tf::Executor::async but does not return a future object">tf::Executor::silent_async</a> returns no future, so an exception thrown inside it is handled contextually (Scenario 3): propagated to the nearest parent context if one exists, or silently suppressed otherwise:</p>
<divclass="ttc" id="aclasstf_1_1Executor_html_a0461cb2c459c9f9473c72af06af9c701"><divclass="ttname"><ahref="classtf_1_1Executor.html#a0461cb2c459c9f9473c72af06af9c701">tf::Executor::silent_async</a></div><divclass="ttdeci">void silent_async(P &&params, F &&func)</div><divclass="ttdoc">similar to tf::Executor::async but does not return a future object</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_a0ce29efa2106c8c5a1432e4a55ab2e05"><divclass="ttname"><ahref="classtf_1_1Runtime.html#a0ce29efa2106c8c5a1432e4a55ab2e05">tf::Runtime::silent_async</a></div><divclass="ttdeci">void silent_async(F &&f)</div><divclass="ttdoc">runs the given function asynchronously without returning any future object</div><divclass="ttdef"><b>Definition</b> runtime.hpp:667</div></div>
</div><!-- fragment --><h1><aclass="anchor" id="ExceptionHandlingCorun"></a>
Catch an Exception from a Corun Loop</h1>
<p><aclass="el" href="classtf_1_1Executor.html#a8fcd9e0557922bb8194999f0cd433ea8" title="runs a target graph and waits until it completes using an internal worker of this executor">tf::Executor::corun</a> and <aclass="el" href="classtf_1_1Runtime.html#aba54a7cacffb54f5eb133730d256a7c4" title="corun all tasks spawned by this runtime with other workers">tf::Runtime::corun</a> run a graph to completion on the calling thread, so any exception thrown inside is immediately rethrown (Scenario 1). You can catch it directly at the <code>corun</code> call site:</p>
<divclass="ttc" id="aclasstf_1_1Executor_html_a8fcd9e0557922bb8194999f0cd433ea8"><divclass="ttname"><ahref="classtf_1_1Executor.html#a8fcd9e0557922bb8194999f0cd433ea8">tf::Executor::corun</a></div><divclass="ttdeci">void corun(T &target)</div><divclass="ttdoc">runs a target graph and waits until it completes using an internal worker of this executor</div></div>
</div><!-- fragment --><p>The same applies to <aclass="el" href="classtf_1_1Runtime.html#aba54a7cacffb54f5eb133730d256a7c4" title="corun all tasks spawned by this runtime with other workers">tf::Runtime::corun</a>:</p>
<divclass="ttc" id="aclasstf_1_1Runtime_html_aba54a7cacffb54f5eb133730d256a7c4"><divclass="ttname"><ahref="classtf_1_1Runtime.html#aba54a7cacffb54f5eb133730d256a7c4">tf::Runtime::corun</a></div><divclass="ttdeci">void corun()</div><divclass="ttdoc">corun all tasks spawned by this runtime with other workers</div><divclass="ttdef"><b>Definition</b> runtime.hpp:642</div></div>
</div><!-- fragment --><p>If the exception is not caught at the <code>corun</code> call site, it propagates to the parent task and then to the parent taskflow:</p>
</div><!-- fragment --><h1><aclass="anchor" id="RetrieveTheExceptionPointerOfATask"></a>
Retrieve the Exception Pointer of a Task</h1>
<p>When multiple tasks throw simultaneously, Taskflow propagates exactly one exception to the application. The remaining exceptions are stored inside their respective tasks and can be inspected via <aclass="el" href="classtf_1_1Task.html#a2f893050f81e40b12df4209bce4fa66b" title="retrieves the exception pointer of this task">tf::Task::exception_ptr</a>, which returns a non-null <code>std::exception_ptr</code> if the task threw, or <code>nullptr</code> if it completed normally.</p>
<p>This is useful for post-mortem analysis: after catching the propagated exception, you can walk the task graph and check which other tasks also threw:</p>
<divclass="ttc" id="aclasstf_1_1Task_html_a2f893050f81e40b12df4209bce4fa66b"><divclass="ttname"><ahref="classtf_1_1Task.html#a2f893050f81e40b12df4209bce4fa66b">tf::Task::exception_ptr</a></div><divclass="ttdeci">std::exception_ptr exception_ptr() const</div><divclass="ttdoc">retrieves the exception pointer of this task</div><divclass="ttdef"><b>Definition</b> task.hpp:1470</div></div>
</div><!-- fragment --><h1><aclass="anchor" id="DisableExceptionHandling"></a>
Disable Exception Handling at Compile Time</h1>
<p>In performance-critical applications without exception safety requirements, you can disable Taskflow's exception handling entirely at compile time by defining <code>TF_DISABLE_EXCEPTION_HANDLING:</code></p>
</div><!-- fragment --><p>This removes all <code>try-<code>catch</code> blocks</code> from the Taskflow runtime, producing a leaner binary and potentially faster execution.</p>
<dlclass="section attention"><dt>Attention</dt><dd>With exception handling disabled, any exception thrown inside a task is unchecked and will propagate uncontrolled through the runtime, likely terminating the program or causing undefined behaviour. Use this option only if your application guarantees that no task will throw. </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! -->