304 lines
47 KiB
HTML
304 lines
47 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh" dir="ltr" class="client-nojs" xmlns="http://www.w3.org/1999/xhtml" xml:lang="zh">
|
|
<head><meta http-equiv="x-ua-compatible" content="ie=edge">
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<title>协程 (C++20)</title>
|
|
<link rel="stylesheet" href="ext.css" />
|
|
<link rel="stylesheet" href="site_modules.css" />
|
|
</head>
|
|
<body class="mediawiki ltr sitedir-ltr ns-0 ns-subject page-cpp_language_coroutines skin-cppreference2 action-view cpp-navbar">
|
|
<div id="cpp-content-base">
|
|
<div id="content"><a id="top"></a>
|
|
<h1 id="firstHeading" class="firstHeading">协程 <span class="t-mark-rev t-since-cxx20">(C++20)</span></h1>
|
|
<div id="bodyContent">
|
|
<div id="contentSub"><span class="subpages">< <a href="cpp.html">cpp</a>‎ | <a href="cpp-language.html">language</a></span></div>
|
|
<div id="mw-content-text" lang="zh" dir="ltr" class="mw-content-ltr" xml:lang="zh">
|
|
<p>协程是能暂停执行以在之后恢复的函数。协程是无栈的:它们通过返回到调用方暂停执行,并且从栈分离存储恢复执行需要的数据。这样就可以编写异步执行的顺序代码(例如不使用显式的回调来处理非阻塞 I/O),还支持对惰性计算的无限序列上的算法及其他用途。</p>
|
|
<p>如果函数的定义进行了下列操作之一,那么它是协程:</p>
|
|
<ul>
|
|
<li>用 <code>co_await</code> 运算符暂停执行,直到恢复:</li>
|
|
</ul>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">task<span class="sy1"><></span> tcp_echo_server<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw4">char</span> data<span class="br0">[</span><span class="nu0">1024</span><span class="br0">]</span><span class="sy4">;</span>
|
|
<span class="kw1">while</span> <span class="br0">(</span><span class="kw2">true</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<a href="cpp-types-size_t.html"><span class="kw100">std::<span class="me2">size_t</span></span></a> n <span class="sy1">=</span> co_await socket.<span class="me1">async_read_some</span><span class="br0">(</span>buffer<span class="br0">(</span>data<span class="br0">)</span><span class="br0">)</span><span class="sy4">;</span>
|
|
co_await async_write<span class="br0">(</span>socket, buffer<span class="br0">(</span>data, n<span class="br0">)</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<ul>
|
|
<li>用关键词 <code>co_yield</code> 暂停执行并返回一个值:</li>
|
|
</ul>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">generator<span class="sy1"><</span><span class="kw4">int</span><span class="sy1">></span> iota<span class="br0">(</span><span class="kw4">int</span> n <span class="sy1">=</span> <span class="nu0">0</span><span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">while</span><span class="br0">(</span><span class="kw2">true</span><span class="br0">)</span>
|
|
co_yield n<span class="sy2">++</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<ul>
|
|
<li>用关键词 <span class="t-lc">co_return</span> 完成执行并返回一个值:</li>
|
|
</ul>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">lazy<span class="sy1"><</span><span class="kw4">int</span><span class="sy1">></span> f<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
co_return <span class="nu0">7</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>每个协程必须具有能够满足一组要求的返回类型,标注于下。</p>
|
|
<h3><span class="mw-headline" id=".E9.99.90.E5.88.B6">限制</span></h3>
|
|
<p>协程不能使用<a href="cpp-language-variadic_arguments.html">变长实参</a>,普通的 <a href="cpp-language-return.html">return</a> 语句,或<a href="cpp-language-function.html">占位符返回类型</a>(<a href="cpp-language-auto.html"><code>auto</code></a> 或 <code>Concept</code>)。</p>
|
|
<p><a href="cpp-language-constexpr.html">constexpr 函数</a>、<a href="cpp-language-constructor.html">构造函数</a>、<a href="cpp-language-destructor.html">析构函数</a>及 <a href="cpp-language-main_function.html"><code>main</code> 函数</a> 不能是协程。</p>
|
|
<h3><span class="mw-headline" id=".E6.89.A7.E8.A1.8C">执行</span></h3>
|
|
<p>每个协程均与下列对象关联:</p>
|
|
<ul>
|
|
<li><i>承诺(promise)对象</i>,从协程内部操纵。协程通过此对象提交其结果或异常。</li>
|
|
<li><i>协程句柄 (coroutine handle)</i>,从协程外部操纵。这是用于恢复协程执行或销毁协程帧的非拥有柄。</li>
|
|
<li><i>协程状态 (coroutine state)</i>,它是一个包含以下各项的分配于堆(除非优化掉其分配)的内部对象:</li>
|
|
</ul>
|
|
<dl>
|
|
<dd>
|
|
<ul>
|
|
<li>承诺对象</li>
|
|
<li>各个形参(全部按值复制)</li>
|
|
<li>当前暂停点的某种表示,使得恢复时程序知晓要从何处继续,销毁时知晓有哪些局部变量在作用域内</li>
|
|
<li>生存期跨过当前暂停点的局部变量和临时量</li>
|
|
</ul>
|
|
</dd>
|
|
</dl>
|
|
<p>当协程开始执行时,它进行下列操作:</p>
|
|
<ul>
|
|
<li>用 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_new.html"><span class="kw687">operator new</span></a></span></span> 分配协程状态对象(见下文)</li>
|
|
<li>将所有函数形参复制到协程状态中:按值传递的形参被移动或复制,按引用传递的参数保持为引用(因此,如果在被指代对象的生存期结束后恢复协程,它可能变成悬垂引用)</li>
|
|
<li>调用承诺对象的构造函数。如果承诺类型拥有接收所有协程形参的构造函数,那么以复制后的协程实参调用该构造函数。否则调用其默认构造函数。</li>
|
|
<li>调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">get_return_object</span><span class="br0">(</span><span class="br0">)</span></span></span> 并将其结果在局部变量中保持。该调用的结果将在协程首次暂停时返回给调用方。至此并包含这个步骤为止,任何抛出的异常均传播回调用方,而非置于承诺中。</li>
|
|
<li>调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">initial_suspend</span><span class="br0">(</span><span class="br0">)</span></span></span> 并 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_await</span></span> 其结果。典型的承诺类型要么(对于惰性启动的协程)返回 <span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">suspend_always</span></span></span>,要么(对于急切启动的协程)返回 <span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">suspend_never</span></span></span>。</li>
|
|
<li>当 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_await promise.<span class="me1">initial_suspend</span><span class="br0">(</span><span class="br0">)</span></span></span> 恢复时,开始协程体的执行。</li>
|
|
</ul>
|
|
<p>当协程抵达暂停点时:</p>
|
|
<ul>
|
|
<li>将先前获得的返回对象返回给调用方/恢复方,如果需要则先隐式转换到协程的返回类型。</li>
|
|
</ul>
|
|
<p>当协程抵达 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_return</span></span> 语句时,它进行下列操作:</p>
|
|
<ul>
|
|
<li>对下列情形调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">return_void</span><span class="br0">(</span><span class="br0">)</span></span></span></li>
|
|
</ul>
|
|
<dl>
|
|
<dd>
|
|
<ul>
|
|
<li><span class="t-c"><span class="mw-geshi cpp source-cpp">co_return<span class="sy4">;</span></span></span></li>
|
|
<li><span class="t-c"><span class="mw-geshi cpp source-cpp">co_return expr</span></span>,其中 expr 具有 void 类型</li>
|
|
<li>控制流出返回 void 的协程的结尾。此时如果承诺类型 Promise 没有 <span class="t-c"><span class="mw-geshi cpp source-cpp">Promise<span class="sy4">::</span><span class="me2">return_void</span><span class="br0">(</span><span class="br0">)</span></span></span> 成员函数,那么则行为未定义。</li>
|
|
</ul>
|
|
</dd>
|
|
</dl>
|
|
<ul>
|
|
<li>或对于 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_return expr</span></span> 调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">return_value</span><span class="br0">(</span>expr<span class="br0">)</span></span></span>,其中 expr 具有非 void 类型</li>
|
|
<li>以创建的逆序销毁所有具有自动存储期的变量。</li>
|
|
<li>调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">final_suspend</span><span class="br0">(</span><span class="br0">)</span></span></span> 并 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_await</span></span> 其结果。</li>
|
|
</ul>
|
|
<p>如果协程因未捕捉的异常结束,那么它进行下列操作:</p>
|
|
<ul>
|
|
<li>捕捉异常并在 catch 块内调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">unhandled_exception</span><span class="br0">(</span><span class="br0">)</span></span></span></li>
|
|
<li>调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">final_suspend</span><span class="br0">(</span><span class="br0">)</span></span></span> 并 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_await</span></span> 其结果(例如,以恢复某个继续或发布其结果)。此时开始恢复协程是未定义行为。</li>
|
|
</ul>
|
|
<p>当经由 <code>co_return</code> 或未捕捉异常而终止协程导致协程状态被销毁,或经由其句柄而导致其被销毁时,它进行下列操作:</p>
|
|
<ul>
|
|
<li>调用承诺对象的析构函数。</li>
|
|
<li>调用各个函数形参副本的析构函数。</li>
|
|
<li>调用 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_delete.html"><span class="kw689">operator delete</span></a></span></span> 以释放协程状态所用的内存。</li>
|
|
<li>转移执行回到调用方/恢复方。</li>
|
|
</ul>
|
|
<h3><span class="mw-headline" id=".E5.A0.86.E5.88.86.E9.85.8D">堆分配</span></h3>
|
|
<p>协程状态由非数组 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_new.html"><span class="kw687">operator new</span></a></span></span> 在堆上分配。</p>
|
|
<p>如果承诺类型定义了类级别的替代函数,那么会使用它,否则会使用全局的 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_new.html"><span class="kw687">operator new</span></a></span></span>。</p>
|
|
<p>如果承诺类型定义了接收额外形参的 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_new.html"><span class="kw687">operator new</span></a></span></span> 的布置形式,且它们所匹配的实参列表中的第一实参是要求的大小(<span class="t-lc"><a href="cpp-types-size_t.html">std::size_t</a></span> 类型),而其余则是各个协程函数实参,那么将这些实参传递给 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_new.html"><span class="kw687">operator new</span></a></span></span>(这使得能对协程使用<a href="cpp-memory-uses_allocator.html#.E4.BD.BF.E7.94.A8.E5.88.86.E9.85.8D.E5.99.A8.E6.9E.84.E9.80.A0">前导分配器约定</a>)</p>
|
|
<p>以下情况下,可以优化掉对 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_new.html"><span class="kw687">operator new</span></a></span></span> 的调用(即使使用了自定义分配器):</p>
|
|
<ul>
|
|
<li>协程状态的生存期严格内嵌于调用方的生存期,且</li>
|
|
<li>协程帧的大小在调用点已知</li>
|
|
</ul>
|
|
<p>此时协程状态嵌入调用方的栈帧(如果调用方是普通函数)或协程状态(如果调用方是协程)之中。</p>
|
|
<p>如果分配失败,那么协程抛出 <span class="t-lc"><a href="cpp-memory-new-bad_alloc.html">std::bad_alloc</a></span>,除非承诺类型 Promise 类型定义了成员函数 <span class="t-c"><span class="mw-geshi cpp source-cpp">Promise<span class="sy4">::</span><span class="me2">get_return_object_on_allocation_failure</span><span class="br0">(</span><span class="br0">)</span></span></span>。如果定义了该成员函数,那么使用 <span class="t-c"><span class="mw-geshi cpp source-cpp"><a href="cpp-memory-new-operator_new.html"><span class="kw687">operator new</span></a></span></span> 的 <span class="t-c"><span class="mw-geshi cpp source-cpp">nothrow</span></span> 形式进行分配,而在分配失败时,协程会立即将从 <span class="t-c"><span class="mw-geshi cpp source-cpp">Promise<span class="sy4">::</span><span class="me2">get_return_object_on_allocation_failure</span><span class="br0">(</span><span class="br0">)</span></span></span> 获得的对象返回给调用方。</p>
|
|
<h3><span class="mw-headline" id=".E6.89.BF.E8.AF.BA.E7.B1.BB.E5.9E.8B.EF.BC.88Promise.EF.BC.89">承诺类型(<span class="t-c"><span class="mw-geshi cpp source-cpp">Promise</span></span>)</span></h3>
|
|
<p>编译器用 <span class="t-lc">std::coroutine_traits</span> 从协程的返回类型确定承诺类型。</p>
|
|
<p>正式而言,令 <code>R</code> 与 <code>Args...</code> 分别代表协程的返回类型与参数类型列表,<code>ClassT</code> 与 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="coMULTI">/*cv限定*/</span></span></span> (如果存在)分别代表协程所属的类与其 cv 限定,如果定义它为非静态成员函数,以如下方式确定它的承诺类型 :</p>
|
|
<ul>
|
|
<li><span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">coroutine_traits</span><span class="sy1"><</span>R, Args...<span class="sy1">></span><span class="sy4">::</span><span class="me2">promise_type</span></span></span>,如果不定义协程为非静态成员函数,</li>
|
|
<li><span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">coroutine_traits</span><span class="sy1"><</span>R, ClassT <span class="coMULTI">/*cv限定*/</span><span class="sy3">&</span>, Args...<span class="sy1">></span><span class="sy4">::</span><span class="me2">promise_type</span></span></span>,如果定义协程为非右值引用限定的非静态成员函数,</li>
|
|
<li><span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">coroutine_traits</span><span class="sy1"><</span>R, ClassT <span class="coMULTI">/*cv限定*/</span><span class="sy3">&&</span>, Args...<span class="sy1">></span><span class="sy4">::</span><span class="me2">promise_type</span></span></span>,如果定义协程为右值引用限定的非静态成员函数。</li>
|
|
</ul>
|
|
<p>例如:</p>
|
|
<ul>
|
|
<li>如果定义协程为 <span class="t-c"><span class="mw-geshi cpp source-cpp">task<span class="sy1"><</span><span class="kw4">float</span><span class="sy1">></span> foo<span class="br0">(</span><a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a> x, <span class="kw4">bool</span> flag<span class="br0">)</span><span class="sy4">;</span></span></span>,那么它的承诺类型是 <span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">coroutine_traits</span><span class="sy1"><</span>task<span class="sy1"><</span><span class="kw4">float</span><span class="sy1">></span>, <a href="cpp-string-basic_string.html"><span class="kw1230">std::<span class="me2">string</span></span></a>, <span class="kw4">bool</span><span class="sy1">></span><span class="sy4">::</span><span class="me2">promise_type</span></span></span>。</li>
|
|
<li>如果定义协程为 <span class="t-c"><span class="mw-geshi cpp source-cpp">task<span class="sy1"><</span><span class="kw4">void</span><span class="sy1">></span> my_class<span class="sy4">::</span><span class="me2">method1</span><span class="br0">(</span><span class="kw4">int</span> x<span class="br0">)</span> <span class="kw4">const</span><span class="sy4">;</span></span></span>,那么它的承诺类型是 <span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">coroutine_traits</span><span class="sy1"><</span>task<span class="sy1"><</span><span class="kw4">void</span><span class="sy1">></span>, <span class="kw4">const</span> my_class<span class="sy3">&</span>, <span class="kw4">int</span><span class="sy1">></span><span class="sy4">::</span><span class="me2">promise_type</span></span></span>。</li>
|
|
<li>如果定义协程为 <span class="t-c"><span class="mw-geshi cpp source-cpp">task<span class="sy1"><</span><span class="kw4">void</span><span class="sy1">></span> my_class<span class="sy4">::</span><span class="me2">method1</span><span class="br0">(</span><span class="kw4">int</span> x<span class="br0">)</span> <span class="sy3">&&</span><span class="sy4">;</span></span></span>,那么它的承诺类型是 <span class="t-c"><span class="mw-geshi cpp source-cpp">std<span class="sy4">::</span><span class="me2">coroutine_traits</span><span class="sy1"><</span>task<span class="sy1"><</span><span class="kw4">void</span><span class="sy1">></span>, my_class<span class="sy3">&&</span>, <span class="kw4">int</span><span class="sy1">></span><span class="sy4">::</span><span class="me2">promise_type</span></span></span>。</li>
|
|
</ul>
|
|
<h3><span class="mw-headline" id="co_await">co_await</span></h3>
|
|
<p>一元运算符 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_await</span></span> 暂停协程并将控制返回给调用方。它的操作数是一个表达式,其类型必须要么定义 <span class="t-c"><span class="mw-geshi cpp source-cpp">operator co_await</span></span>,要么能以当前协程的 <span class="t-c"><span class="mw-geshi cpp source-cpp">Promise<span class="sy4">::</span><span class="me2">await_transform</span></span></span> 转换到这种类型。</p>
|
|
<table class="t-sdsc-begin">
|
|
<tr>
|
|
<td colspan="10" class="t-sdsc-sep"></td>
|
|
</tr>
|
|
<tr class="t-sdsc">
|
|
<td class="t-sdsc-nopad"><code><b>co_await</b></code> <span class="t-spar">表达式</span></td>
|
|
<td class="t-sdsc-nopad"></td>
|
|
<td class="t-sdsc-nopad"></td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="10" class="t-sdsc-sep"></td>
|
|
</tr>
|
|
</table>
|
|
<p>首先,以下列方式将 <span class="t-spar">表达式</span> 转换成可等待体(awaitable):</p>
|
|
<ul>
|
|
<li>如果 <span class="t-spar">表达式</span> 由初始暂停点、最终暂停点或 yield 表达式所产生,那么可等待体是 <span class="t-spar">表达式</span> 本身。</li>
|
|
<li>否则,如果当前协程的承诺类型拥有成员函数 <span class="t-c"><span class="mw-geshi cpp source-cpp">await_transform</span></span>,那么可等待体是 <span class="t-c"><span class="mw-geshi cpp source-cpp">promise.<span class="me1">await_transform</span><span class="br0">(</span>表达式<span class="br0">)</span></span></span>。</li>
|
|
<li>否则,可等待体是 <span class="t-spar">表达式</span> 本身。</li>
|
|
</ul>
|
|
<p>然后以下列方式获得等待器(awaiter)对象:</p>
|
|
<ul>
|
|
<li>如果针对 <span class="t-c"><span class="mw-geshi cpp source-cpp">operator co_await</span></span> 的重载决议给出单个最佳重载,那么等待器是该调用的结果(对于成员重载为 <span class="t-c"><span class="mw-geshi cpp source-cpp">awaitable.<span class="me1">operator</span> co_await<span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span></span></span>,对于非成员重载为 <span class="t-c"><span class="mw-geshi cpp source-cpp">operator co_await<span class="br0">(</span><span class="kw1">static_cast</span><span class="sy1"><</span>Awaitable<span class="sy3">&&</span><span class="sy1">></span><span class="br0">(</span>awaitable<span class="br0">)</span><span class="br0">)</span><span class="sy4">;</span></span></span>)</li>
|
|
<li>否则,如果重载决议找不到 <code>operator co_await</code>,那么等待器是可等待体本身</li>
|
|
<li>否则,如果重载决议有歧义,那么程序非良构</li>
|
|
</ul>
|
|
<p>如果上述表达式为纯右值,那么等待器对象是从它<a href="cpp-language-implicit_conversion.html#.E4.B8.B4.E6.97.B6.E9.87.8F.E5.AE.9E.E8.B4.A8.E5.8C.96">实质化</a>的临时量。否则,如果上述表达式为泛左值,那么等待器对象是它所指代的对象。</p>
|
|
<p>然后,调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">awaiter.<span class="me1">await_ready</span><span class="br0">(</span><span class="br0">)</span></span></span>(这是当已知结果就绪或可以同步完成时,用以避免暂停开销的快捷方式)。如果它的结果按语境转换成 <code>bool</code> 为 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw2">false</span></span></span>,那么:</p>
|
|
<dl>
|
|
<dd>暂停协程(以各局部变量和当前暂停点填充其协程状态)。</dd>
|
|
<dd>调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">awaiter.<span class="me1">await_suspend</span><span class="br0">(</span>handle<span class="br0">)</span></span></span>,其中 handle 是表示当前协程的协程句柄。这个函数内部可以通过这个句柄观察暂停的协程,而且此函数负责调度它以在某个执行器上恢复,或将其销毁(并返回 <code>false</code> 当做调度)
|
|
<ul>
|
|
<li>如果 <code>await_suspend</code> 返回 <code>void</code>,那么立即将控制返回给当前协程的调用方/恢复方(此协程保持暂停),否则</li>
|
|
<li>如果 <code>await_suspend</code> 返回 <code>bool</code>,那么:</li>
|
|
</ul>
|
|
<dl>
|
|
<dd>
|
|
<ul>
|
|
<li>值为 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw2">true</span></span></span> 时将控制返回给当前协程的调用方/恢复方</li>
|
|
<li>值为 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="kw2">false</span></span></span> 时恢复当前协程。</li>
|
|
</ul>
|
|
</dd>
|
|
</dl>
|
|
<ul>
|
|
<li>如果 <code>await_suspend</code> 返回某个其他协程的协程句柄,那么(通过调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">handle.<span class="me1">resume</span><span class="br0">(</span><span class="br0">)</span></span></span>)恢复该句柄(注意这可以连锁进行,并最终导致当前协程恢复)</li>
|
|
<li>如果 <code>await_suspend</code> 抛出异常,那么捕捉该异常,恢复协程,并立即重抛异常</li>
|
|
</ul>
|
|
</dd>
|
|
<dd>最后,调用 <span class="t-c"><span class="mw-geshi cpp source-cpp">awaiter.<span class="me1">await_resume</span><span class="br0">(</span><span class="br0">)</span></span></span>,它的结果就是整个 <span class="t-c"><span class="mw-geshi cpp source-cpp">co_await expr</span></span> 表达式的结果。</dd>
|
|
</dl>
|
|
<p>如果协程在 co_await 表达式中暂停而在后来恢复,那么恢复点处于紧接对 <span class="t-c"><span class="mw-geshi cpp source-cpp">awaiter.<span class="me1">await_resume</span><span class="br0">(</span><span class="br0">)</span></span></span> 的调用之前。</p>
|
|
<p>注意,因为协程在进入 <span class="t-c"><span class="mw-geshi cpp source-cpp">awaiter.<span class="me1">await_suspend</span><span class="br0">(</span><span class="br0">)</span></span></span> 前已经完全暂停,所以该函数可以自由地在线程间转移协程柄而无需额外同步。例如,可以将它放入回调,将它调度成在异步 I/O 操作完成时在线程池上运行等。此时因为当前协程可能已被恢复,从而执行了等待器的析构函数,同时由于 <code>await_suspend()</code> 在当前线程上持续执行, <code>await_suspend()</code> 应该把 <span class="t-c"><span class="mw-geshi cpp source-cpp"><span class="sy2">*</span>this</span></span> 当作已被销毁并且在柄被发布到其他线程后不再访问它。</p>
|
|
<h3><span class="mw-headline" id=".E7.A4.BA.E4.BE.8B">示例</span></h3>
|
|
<div class="t-example">
|
|
<div class="t-example-live-link"></div>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1"><span class="co2">#include <coroutine></span>
|
|
<span class="co2">#include <iostream></span>
|
|
<span class="co2">#include <stdexcept></span>
|
|
<span class="co2">#include <thread></span>
|
|
|
|
<span class="kw4">auto</span> switch_to_new_thread<span class="br0">(</span>std<span class="sy4">::</span><span class="me2">jthread</span><span class="sy3">&</span> out<span class="br0">)</span> <span class="br0">{</span>
|
|
<span class="kw1">struct</span> awaitable <span class="br0">{</span>
|
|
std<span class="sy4">::</span><span class="me2">jthread</span><span class="sy2">*</span> p_out<span class="sy4">;</span>
|
|
<span class="kw4">bool</span> await_ready<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <span class="kw1">return</span> <span class="kw2">false</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
<span class="kw4">void</span> await_suspend<span class="br0">(</span>std<span class="sy4">::</span><span class="me2">coroutine_handle</span><span class="sy1"><></span> h<span class="br0">)</span> <span class="br0">{</span>
|
|
std<span class="sy4">::</span><span class="me2">jthread</span><span class="sy3">&</span> out <span class="sy1">=</span> <span class="sy2">*</span>p_out<span class="sy4">;</span>
|
|
<span class="kw1">if</span> <span class="br0">(</span>out.<span class="me1">joinable</span><span class="br0">(</span><span class="br0">)</span><span class="br0">)</span>
|
|
<span class="kw1">throw</span> <a href="cpp-error-runtime_error.html"><span class="kw770">std::<span class="me2">runtime_error</span></span></a><span class="br0">(</span><span class="st0">"jthread 输出参数非空"</span><span class="br0">)</span><span class="sy4">;</span>
|
|
out <span class="sy1">=</span> std<span class="sy4">::</span><span class="me2">jthread</span><span class="br0">(</span><span class="br0">[</span>h<span class="br0">]</span> <span class="br0">{</span> h.<span class="me1">resume</span><span class="br0">(</span><span class="br0">)</span><span class="sy4">;</span> <span class="br0">}</span><span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="co1">// 潜在的未定义行为:访问潜在被销毁的 *this</span>
|
|
<span class="co1">// std::cout << "新线程 ID:" << p_out->get_id() << "\n";</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"新线程 ID:"</span> <span class="sy1"><<</span> out.<span class="me1">get_id</span><span class="br0">(</span><span class="br0">)</span> <span class="sy1"><<</span> <span class="st0">"<span class="es1">\n</span>"</span><span class="sy4">;</span> <span class="co1">// 这样可以</span>
|
|
<span class="br0">}</span>
|
|
<span class="kw4">void</span> await_resume<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span><span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="kw1">return</span> awaitable<span class="br0">{</span><span class="sy3">&</span>out<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw1">struct</span> task <span class="br0">{</span>
|
|
<span class="kw1">struct</span> promise_type <span class="br0">{</span>
|
|
task get_return_object<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <span class="kw1">return</span> <span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
std<span class="sy4">::</span><span class="me2">suspend_never</span> initial_suspend<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span> <span class="kw1">return</span> <span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
std<span class="sy4">::</span><span class="me2">suspend_never</span> final_suspend<span class="br0">(</span><span class="br0">)</span> <span class="kw1">noexcept</span> <span class="br0">{</span> <span class="kw1">return</span> <span class="br0">{</span><span class="br0">}</span><span class="sy4">;</span> <span class="br0">}</span>
|
|
<span class="kw4">void</span> return_void<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span><span class="br0">}</span>
|
|
<span class="kw4">void</span> unhandled_exception<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span><span class="br0">}</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
<span class="br0">}</span><span class="sy4">;</span>
|
|
|
|
task resuming_on_new_thread<span class="br0">(</span>std<span class="sy4">::</span><span class="me2">jthread</span><span class="sy3">&</span> out<span class="br0">)</span> <span class="br0">{</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"协程开始,线程 ID:"</span> <span class="sy1"><<</span> <a href="cpp-thread-get_id.html"><span class="kw2148">std::<span class="me2">this_thread</span><span class="sy4">::</span><span class="me2">get_id</span></span></a><span class="br0">(</span><span class="br0">)</span> <span class="sy1"><<</span> <span class="st0">"<span class="es1">\n</span>"</span><span class="sy4">;</span>
|
|
co_await switch_to_new_thread<span class="br0">(</span>out<span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="co1">// 等待器在此销毁</span>
|
|
<a href="cpp-io-cout.html"><span class="kw1758">std::<span class="me2">cout</span></span></a> <span class="sy1"><<</span> <span class="st0">"协程恢复,线程 ID:"</span> <span class="sy1"><<</span> <a href="cpp-thread-get_id.html"><span class="kw2148">std::<span class="me2">this_thread</span><span class="sy4">::</span><span class="me2">get_id</span></span></a><span class="br0">(</span><span class="br0">)</span> <span class="sy1"><<</span> <span class="st0">"<span class="es1">\n</span>"</span><span class="sy4">;</span>
|
|
<span class="br0">}</span>
|
|
|
|
<span class="kw4">int</span> main<span class="br0">(</span><span class="br0">)</span> <span class="br0">{</span>
|
|
std<span class="sy4">::</span><span class="me2">jthread</span> out<span class="sy4">;</span>
|
|
resuming_on_new_thread<span class="br0">(</span>out<span class="br0">)</span><span class="sy4">;</span>
|
|
<span class="br0">}</span></pre></div>
|
|
</div>
|
|
<p>可能的输出:</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="text source-text">
|
|
<pre class="de1">协程开始,线程 ID:139972277602112
|
|
新线程 ID:139972267284224
|
|
协程恢复,线程 ID:139972267284224</pre></div>
|
|
</div>
|
|
</div>
|
|
<p>注意:等待器对象是协程状态的一部分(作为生存期跨过暂停点的临时量),并且在 co_await 表达式结束前销毁。可以用它维护某些异步 I/O API 所要求的每操作内状态,而无需用到额外的堆分配。</p>
|
|
<p>标准库定义了两个平凡的可等待体:<span class="t-lc">std::suspend_always</span> 及 <span class="t-lc">std::suspend_never</span>。</p>
|
|
<table class="metadata plainlinks ambox mbox-small-left ambox-notice" style="">
|
|
<tr>
|
|
<td class="mbox-empty-cell"></td>
|
|
<td class="mbox-text" style="">本节未完成<br />
|
|
原因:示例</td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id="co_yield">co_yield</span></h3>
|
|
<p>yield 表达式向调用方返回一个值并暂停当前协程:它是可恢复生成器函数的常用构建块</p>
|
|
<table class="t-sdsc-begin">
|
|
<tr>
|
|
<td colspan="10" class="t-sdsc-sep"></td>
|
|
</tr>
|
|
<tr class="t-sdsc">
|
|
<td class="t-sdsc-nopad"><code><b>co_yield</b></code> <span class="t-spar">表达式</span></td>
|
|
<td class="t-sdsc-nopad"></td>
|
|
<td class="t-sdsc-nopad"></td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="10" class="t-sdsc-sep"></td>
|
|
</tr>
|
|
<tr class="t-sdsc">
|
|
<td class="t-sdsc-nopad"><code><b>co_yield</b></code> <span class="t-spar">花括号初始化器列表</span></td>
|
|
<td class="t-sdsc-nopad"></td>
|
|
<td class="t-sdsc-nopad"></td>
|
|
</tr>
|
|
<tr>
|
|
<td colspan="10" class="t-sdsc-sep"></td>
|
|
</tr>
|
|
</table>
|
|
<p>它等价于</p>
|
|
<div dir="ltr" class="mw-geshi" style="text-align: left;">
|
|
<div class="cpp source-cpp">
|
|
<pre class="de1">co_await promise.<span class="me1">yield_value</span><span class="br0">(</span>表达式<span class="br0">)</span></pre></div>
|
|
</div>
|
|
<p>典型的生成器的 <code>yield_value</code> 会将其实参存储(复制/移动或仅存储其地址,因为实参的生存期跨过 co_await 内的暂停点)到生成器对象中并返回 <span class="t-lc">std::suspend_always</span>,将控制转移给调用方/恢复方。</p>
|
|
<table class="metadata plainlinks ambox mbox-small-left ambox-notice" style="">
|
|
<tr>
|
|
<td class="mbox-empty-cell"></td>
|
|
<td class="mbox-text" style="">本节未完成<br />
|
|
原因:示例</td>
|
|
</tr>
|
|
</table>
|
|
<h3><span class="mw-headline" id=".E5.BA.93.E6.94.AF.E6.8C.81">库支持</span></h3>
|
|
<p><a href="cpp-coroutine.html">协程支持库</a>定义数个类型,提供协程的编译与运行时支持。</p>
|
|
</div>
|
|
<div class="visualClear"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|