C++ Coroutines Understanding operator co_await

来源:https://lewissbaker.github.io/2017/11/17/understanding-operator-co-await

What does the Coroutines TS give us?

The facilities the C++ Coroutines TS provides in the language can be thought of as a low-level assembly-language for coroutines. These facilities can be difficult to use directly in a safe way and are mainly intended to be used by library-writers to build higher-level abstractions that application developers can work with safely.

C ++ Coroutines TS 提供的功能可以被认为是协程的一种低级汇编语言。这些功能可能很难以安全的方式直接使用,并且主要旨在供库编写人员用来构建更高级的抽象,以便应用程序开发人员可以安全地使用它们。

Compiler <-> Library interaction

Instead, it specifies a general mechanism for library code to customise the behaviour of the coroutine by implementing types that conform to a specific interface. The compiler then generates code that calls methods on instances of types provided by the library.

它为库代码指定了一种通用机制,可通过 实现符合特定接口的类型 来自定义协程的行为。然后编译器调用 这些类型的方法 = 库提供的类型的实例的方法

There are two kinds of interfaces that are defined by the coroutines TS: The Promise interface and the Awaitable interface.

The Promise interface specifies methods for customising the behaviour of the coroutine itself. The library-writer is able to customise what happens when the coroutine is called, what happens when the coroutine returns (either by normal means or via an unhandled exception) and customise the behaviour of any co_await or co_yield expression within the coroutine.

用于定义协程的行为。

The Awaitable interface specifies methods that control the semantics of a co_await expression. When a value is co_awaited, the code is translated into a series of calls to methods on the awaitable object that allow it to specify: whether to suspend the current coroutine, execute some logic after it has suspended to schedule the coroutine for later resumption, and execute some logic after the coroutine resumes to produce the result of the co_await expression.

控制 co_await 表达式的语义。

Awaiters and Awaitables: Explaining operator co_await

Awaitable has 2 kinds:

  • Normal Awatable : 原生支持 operator co_await()
  • Contextually Awaitable : 原生不支持 operator co_await() , 但在特定的 Promise 上下文中,通过 Promise::await_transform() 转化后,返回值支持 operator co_await()

An Awaiter type is a type that implements the 3 special methods that are called as part of a co_await expression:

  • await_ready()
  • await_suspend()
  • await_resume()

Note that a type can be both an Awaitable type and an Awaiter type.

具体到某个类型,是可以同时满足 Awaitable 的 Awaiter 要求的。

Obtaining the Awaiter

-> Awaitable -> Awaiter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 没有转换就直接用
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);
}

Awaiting the Awaiter

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
{
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();
}

It is the responsibility of the await_suspend() method to schedule the coroutine for resumption (or destruction) at some point in the future once the operation has completed. Note that returning false from await_suspend() counts as scheduling the coroutine for immediate resumption on the current thread.

The return-value of the await_resume() method call becomes the result of the co_await expression.

Coroutine Handles

其实现可以在 <experimental\resumable> 头文件中找到。

此类型提供的接口中:

  • resume() 最常用
  • 避免使用 destroy().promise(),库作者除外

You should generally not need to (and indeed should really avoid) calling .destroy() unless you are a library writer implementing the coroutine promise type. Normally, coroutine frames will be owned by some kind of RAII type returned from the call to the coroutine. So calling .destroy() without cooperation with the RAII object could lead to a double-destruction bug.

The .promise() method returns a reference to the coroutine’s promise object. However, like .destroy(), it is generally only useful if you are authoring coroutine promise types. You should consider the coroutine’s promise object as an internal implementation detail of the coroutine. For most Normally Awaitable types you should use coroutine_handle<void> as the parameter type to the await_suspend() method instead of coroutine_handle<Promise>.

此章节有很多针对库开发者、使用者的建议。

Synchronisation-free async code

免同步的异步代码

An example: Implementing a simple thread-synchronisation primitive

TODO