这篇讲的不错: C++20 Coroutine实例教学 - 知乎 (zhihu.com)
图:
https://pic1.zhimg.com/80/v2-5d11f4e5b9eb2b4f5e692b73baa69f78_720w.webp
从上图可以看出c++ coroutine20实现的几个特点:
- 依赖coroutine_handle<>对象管理协程本身的生命周期。
- 依赖promise_type对象对协程的一些行为(如启动挂起, 执行结束前挂起等)进行配置, 传递返回值
- co_await机制配合Awaitable对象完成协程的挂起点定义以及协程与外界的数据交换。
三个运算符:
协程其实也是一个函数
区分协程与普通函数:只有以下三个运算符其中一个,则代表这个函数是协程
- co_await
- co_yeild
- co_return
等待体
等待体(Awaiter)。co_await 后面接的对象都是Awaiter,需要实现三个函数:
-
bool await_ready(): co_wait awaiter时,先调用awaiter.await_ready() :如果返回 true,则表示已经就绪,无需挂起;如果返回 false, 表示需要挂起,则接着调用awaiter.await_suspend()
-
??? await_suspend(std::coroutine_handle<> coroutine_handle):
await_suspend
是协程的挂起点(suspend point),用于指定在协程挂起时要执行的操作。参数 coroutine_handle 用来表示当前协程 . 注意到 await_suspend 函数的返回值类型我们没有明确给出,因为它有以下几种选项:- 返回 void 类型或者返回 true,表示当前协程挂起之后将执行权还给当初调用或者恢复当前协程的函数。
- 返回 false,则恢复执行当前协程。注意此时不同于 await_ready 返回 true 的情形,此时协程已经挂起,await_suspend 返回 false 相当于挂起又立即恢复。
- 返回其他协程的 coroutine_handle 对象,这时候返回的 coroutine_handle 对应的协程被恢复执行。
- 抛出异常,此时当前协程恢复执行,并在当前协程当中抛出异常。
-
??? await_resume():
await_resume
是协程的一个特殊成员函数,用于指定在协程恢复执行时要返回的值。在 cpp 中,await_resume
函数通常用于异步操作的结果返回。当协程在co_await
表达式处被暂停,并且异步操作完成后,协程将恢复执行,并调用与co_await
表达式关联的await_resume
函数来获取异步操作的结果。await_resume
函数应该返回一个值,这个值就是co_await
表达式的结果。这个结果可以是任何类型,取决于你的异步操作的返回类型以及你希望在协程中使用的数据类型。
最简单的两个Awaiter
struct suspend_never {
constexpr bool await_ready() const noexcept {
return true; // 返回 true,总是不挂起
}
...
};
struct suspend_always {
constexpr bool await_ready() const noexcept {
return false; // 返回 false,总是挂起
}
...
};
协程的返回值类型
协程的返回值类型Result:
我们要在c++20中定义一个coroutine, 对函数的返回值是有要求的, 这个返回值的类型必须有一个嵌套的子类型promise_type。
- Result中必须要有一个类型promise_type, promise_type中必须实现以下函数:
- initial_suspend():
initial_suspend()
是协程的一个特殊成员函数,用于指定在协程开始执行时是否立即暂停。在 cpp 中,initial_suspend()
函数返回一个std::suspend_always
或std::suspend_never
类型的对象,决定了协程是否在开始执行时暂停。当然也可以返回其他自定义的Awaiter。 - Result get_return_object():
get_return_object()
的作用是为协程创建一个协程对象,并在协程开始执行之前,为其进行初始化和资源分配。get_return_object()
函数在协程的创建阶段被调用,用于创建与协程关联的协程对象,并在协程开始执行之前进行初始化和资源分配。 - void return_value(int value)和void return_void():在co_returm时调用
- void unhandled_exception():抛出异常
- final_suspend():
final_suspend()
函数返回一个表示协程最终挂起点的 awaitable 对象,通常是std::suspend_never
或std::suspend_always
。这个返回值告诉编译器在协程执行完毕后是否需要额外的挂起操作。
- initial_suspend():
所以Result就是这样的:
class Result {
struct promise_type {
Awaitable initial_suspend() {
// ...
return Awaiter;
}
Awaitable final_suspend() {
// ...
return Awaiter;
}
Result get_return_object() {
// ...
return Result{std::coroutine_handle<promise_type>::from_promise(*this)};
}
void return_value(int value) { // value是co_return value时传入
// ...
}
void unhandled_exception() noexcept {
// std::current_exception() 获取当前异常
处理异常
}
// 。。。。。。
};
};
static coroutine_handle from_promise(_Promise& _Prom) noexcept:通过 promise_type 的对象的地址获取 coroutine_handle 的函数,它实际上是 coroutine_handle 的一个静态函数。可以使用是这个函数保存当前协程的句柄。
对于
co_await <expr>
表达式当中expr
的处理,cpp 有一套完善的流程:
- 如果 promise_type 当中定义了 await_transform 函数,那么先通过
promise.await_transform(expr)
来对 expr 做一次转换,得到的对象称为 awaitable;否则 awaitable 就是 expr 本身。auto operator co_await(int value) { struct IntAwaiter { int value; bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle<Generator::promise_type> handle) const { handle.promise().value = value; } void await_resume() { } }; return IntAwaiter{.value = value}; }
- 接下来使用 awaitable 对象来获取等待体(awaiter)。如果 awaitable 对象有
operator co_await
运算符重载,那么等待体就是operator co_await(awaitable)
,否则等待体就是 awaitable 对象本身。struct Generator { struct promise_type { int value; // 传值的同时要挂起,值存入 value 当中 std::suspend_always await_transform(int value) { this->value = value; return {}; } ... }; std::coroutine_handle<promise_type> handle; int next() { handle.resume(); // 外部调用者或者恢复者可以通过读取 value return handle.promise().value; } };
定义了
await_transform
函数之后,co_await expr
就相当于co_await promise.await_transform(expr)
了。
- 使用 co_yield: co_yield expr
等价于
co_await promise.yield_value(expr)struct promise_type { ... // 将 await_transform 替换为 yield_value std::suspend_always yield_value(int value) { this->value = value; is_ready = true; return {}; } ... };
定义一个类型
Task
来作为协程的返回值。Task
类型可以用来封装任何返回结果的异步行为定义以
Task<ResultType>
为返回值类型的协程,并且可以在协程内部使用co_await
来等待其他Task
的执行。
- 需要一个结果类型来承载正常返回和异常抛出的情况。
- 需要为
Task
定义相应的promise_type
类型来支持co_return
和co_await
。- 为
Task
实现获取结果的阻塞0函数get_result
或者用于获取返回值的回调then
以及用于获取抛出的异常的回调catching
。结果类型的定义:
#include <exception> template<typename T> struct Result { // 初始化为默认值 explicit Result() = default; // 当 Task 正常返回时用结果初始化 Result explicit Result(T &&value) : _value(value) {} // 当 Task 抛异常时用异常初始化 Result explicit Result(std::exception_ptr &&exception_ptr) : _exception_ptr(exception_ptr) {} // 读取结果,有异常则抛出异常 T get_or_throw() { if (_exception_ptr) { std::rethrow_exception(_exception_ptr); } return _value; } private: T _value{}; std::exception_ptr _exception_ptr; };
promise_type 的定义:
template<typename ResultType> struct TaskPromise { // 协程立即执行 std::suspend_never initial_suspend() { return {}; } // 执行结束后挂起,等待外部销毁。该逻辑与前面的 Generator 类似 std::suspend_always final_suspend() noexcept { return {}; } // 构造协程的返回值对象 Task Task<ResultType> get_return_object() { return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)}; } void unhandled_exception() { // 将异常存入 result result = Result<ResultType>(std::current_exception()); } void return_value(ResultType value) { // 将返回值存入 result,对应于协程内部的 'co_return value' result = Result<ResultType>(std::move(value)); } private: // 使用 std::optional 可以区分协程是否执行完成 std::optional<Result<ResultType>> result; };
await_transform:
template<typename ResultType> struct TaskPromise { ... // 注意这里的模板参数 template<typename _ResultType> TaskAwaiter<_ResultType> await_transform(Task<_ResultType> &&task) { return TaskAwaiter<_ResultType>(std::move(task)); } ... }
TaskAwaiter
的定义:template<typename R> struct TaskAwaiter { explicit TaskAwaiter(Task<R> &&task) noexcept : task(std::move(task)) {} TaskAwaiter(TaskAwaiter &&completion) noexcept : task(std::exchange(completion.task, {})) {} TaskAwaiter(TaskAwaiter &) = delete; TaskAwaiter &operator=(TaskAwaiter &) = delete; constexpr bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle<> handle) noexcept { // 当 task 执行完之后调用 resume task.finally([handle]() { handle.resume(); }); } // 协程恢复执行时,被等待的 Task 已经执行完,调用 get_result 来获取结果 R await_resume() noexcept { return task.get_result(); } private: Task<R> task; };
同步阻塞获取结果:
template<typename ResultType> struct TaskPromise { ... void unhandled_exception() { std::lock_guard lock(completion_lock); result = Result<ResultType>(std::current_exception()); // 通知 get_result 当中的 wait completion.notify_all(); } void return_value(ResultType value) { std::lock_guard lock(completion_lock); result = Result<ResultType>(std::move(value)); // 通知 get_result 当中的 wait completion.notify_all(); } ResultType get_result() { // 如果 result 没有值,说明协程还没有运行完,等待值被写入再返回 std::unique_lock lock(completion_lock); if (!result.has_value()) { // 等待写入值之后调用 notify_all completion.wait(lock); } // 如果有值,则直接返回(或者抛出异常) return result->get_or_throw(); } private: std::optional<Result<ResultType>> result; std::mutex completion_lock; std::condition_variable completion; }
异步结果回调
异步回调的实现稍微复杂一些,其实主要复杂在对于函数的运用。实际上对于回调的支持,主要就是支持回调的注册和回调的调用。根据结果类型的不同,回调又分为返回值的回调或者抛出异常的回调:template<typename ResultType> struct TaskPromise { ... void unhandled_exception() { std::lock_guard lock(completion_lock); result = Result<ResultType>(std::current_exception()); completion.notify_all(); // 调用回调 notify_callbacks(); } void return_value(ResultType value) { std::lock_guard lock(completion_lock); result = Result<ResultType>(std::move(value)); completion.notify_all(); // 调用回调 notify_callbacks(); } void on_completed(std::function<void(Result<ResultType>)> &&func) { std::unique_lock lock(completion_lock); // 加锁判断 result if (result.has_value()) { // result 已经有值 auto value = result.value(); // 解锁之后再调用 func lock.unlock(); func(value); } else { // 否则添加回调函数,等待调用 completion_callbacks.push_back(func); } } private: ... // 回调列表,我们允许对同一个 Task 添加多个回调 std::list<std::function<void(Result<ResultType>)>> completion_callbacks; void notify_callbacks() { auto value = result.value(); for (auto &callback : completion_callbacks) { callback(value); } // 调用完成,清空回调 completion_callbacks.clear(); } }
Task的实现:
template<typename ResultType> struct Task { // 声明 promise_type 为 TaskPromise 类型 using promise_type = TaskPromise<ResultType>; ResultType get_result() { return handle.promise().get_result(); } Task &then(std::function<void(ResultType)> &&func) { handle.promise().on_completed([func](auto result) { try { func(result.get_or_throw()); } catch (std::exception &e) { // 忽略异常 } }); return *this; } Task &catching(std::function<void(std::exception &)> &&func) { handle.promise().on_completed([func](auto result) { try { // 忽略返回值 result.get_or_throw(); } catch (std::exception &e) { func(e); } }); return *this; } Task &finally(std::function<void()> &&func) { handle.promise().on_completed([func](auto result) { func(); }); return *this; } explicit Task(std::coroutine_handle<promise_type> handle) noexcept: handle(handle) {} Task(Task &&task) noexcept: handle(std::exchange(task.handle, {})) {} Task(Task &) = delete; Task &operator=(Task &) = delete; ~Task() { if (handle) handle.destroy(); } private: std::coroutine_handle<promise_type> handle; };