原文链接
和上一篇文章一样,感觉这个文章写得也蛮好的,链接如上。
interface
interface主要分为两种:promise和awaitable。本文将主要讲解promise.
promise
awaiters & awaitable
co_await 必须在context中使用,支持co_await的类型就叫做awaitable类型。
co_await必须再context中使用。
awaitable可以分为两个类型:
- Normally Awaitable:promise,没有使用await_transform
- Contextually Awaitbale: 使用了await_transform
awaitable类型主要包括三个部分:
3. await_ready
4. await_suspend
5. await_resume
注意区分awaiter和awaitable,下面这个例子是原文里的:
template<typename P, typename T>
decltype(auto) get_awaitable(P& promise, T&& expr)
{
if constexpr (has_any_await_transform_member_v<P>)
return promise.await_transform(static_cast<T&&>(expr));
else
return static_cast<T&&>(expr);
}
template<typename Awaitable>
decltype(auto) get_awaiter(Awaitable&& awaitable)
{
if constexpr (has_member_operator_co_await_v<Awaitable>)
return static_cast<Awaitable&&>(awaitable).operator co_await();
else if constexpr (has_non_member_operator_co_await_v<Awaitable&&>)
return operator co_await(static_cast<Awaitable&&>(awaitable));
else
return static_cast<Awaitable&&>(awaitable);
}
在完成了封装之后,co_await(<expr>)
可以被看做下面这个代码段:
{
auto&& value = <expr>;
auto&& awaitable = get_awaitable(promise, static_cast<decltype(value)>(value));
auto&& awaiter = get_awaiter(static_cast<decltype(awaitable)>(awaitable));
if (!awaiter.await_ready())
{
using handle_t = std::experimental::coroutine_handle<P>;
using await_suspend_result_t =
decltype(awaiter.await_suspend(handle_t::from_promise(p)));
<suspend-coroutine>
if constexpr (std::is_void_v<await_suspend_result_t>)
{
awaiter.await_suspend(handle_t::from_promise(p));
<return-to-caller-or-resumer>
}
else
{
static_assert(
std::is_same_v<await_suspend_result_t, bool>,
"await_suspend() must return 'void' or 'bool'.");
if (awaiter.await_suspend(handle_t::from_promise(p)))
{
<return-to-caller-or-resumer>
}
}
<resume-point>
}
return awaiter.await_resume();
}
await_suspende
分为两种返回值类型:
- void返回类型:无条件地转回协程的caller或resumer处
- bool返回类型:有条件执行,尤其是在当异步操作被同步完成时,会返回false。也就是说,可以通过bool的返回类型来判断是否实现了异步操作。
await_resume
避免<suspend_coroutine>的损耗,可以实现无需暂停的同步完成。其返回值就是co_await 的结果。
关于异常处理(exception)
在await_resume中,可以抛出异常,并在抛出异常时自动resume,回到协程中去。
Coroutine handle
coroutine handle代表了一个coroutine frame的没有所有权的句柄。
协程句柄类型有以下的接口:
namespace std::experimental
{
template<typename Promise>
struct coroutine_handle;
template<>
struct coroutine_handle<void>
{
bool done() const;
void resume();
void destroy();
void* address() const;
static coroutine_handle from_address(void* address);
};
template<typename Promise>
struct coroutine_handle : coroutine_handle<void>
{
Promise& promise() const;
static coroutine_handle from_promise(Promise& promise);
static coroutine_handle from_address(void* address);
};
}
上述所写到的三个函数如下:
- resume,重新激活在resume_point的被暂停的协程。
- destroy, 释放coroutine frame所使用的内存。由于RAII,大部分的释放占用内存的工作由操作系统完成,如果不是库的作者的话,尽量不要使用destroy.
- promise, 返回一个promise object。
- address from_address , 允许句柄和void*的互相转换。这一步主要是为了将context作为参数传递到别的C++的API里。
Synchronisation-free async code
当协程已经挂起时,通过在await_suspend()中启动异步读取操作意味着我们可以在操作完成时恢复协程,而不需要任何线程同步来协调启动操作的线程和完成操作的线程,以下为一个原文中的例子:
Time Thread 1 Thread 2
| -------- --------
| .... Call OS - Wait for I/O event
| Call await_ready() |
| <supend-point> |
| Call await_suspend(handle) |
| Store handle in operation |
V Start AsyncFileRead ---+ V
+-----> <AsyncFileRead Completion Event>
Load coroutine_handle from operation
Call handle.resume()
<resume-point>
Call to await_resume()
execution continues....
Call to AsyncFileRead returns
Call to await_suspend() returns
<return-to-caller/resumer>
当Thread1到达了suspend_point时,调用await_suspend,并将相关的数据进行存储,存储到coroutine frame中,此时thread1已经挂起,一个新的coroutine frame已经被构造,以供Thread2使用。
Thread2 加载了协程句柄,并进行相关的操作。通过调用句柄的resume()到达resume_point并在达到了该点后,调用await_resume,继续计算直到重新返回Thread1。await_resume的返回值就是co_await操作的返回值。
在获取了await_resume的返回值后,协程会立刻销毁Awaiter对象,之后继续运行。
东西太多了,理解起来也费劲,剩下的一些和内存相关的我将在下一节继续补充说明。