2024-09-11
最后编辑于:2024-09-15
C++20的协程为非对称协程(Asymmetric Coroutine)。其中协程只能通过一个特殊的操作(通常是yield
)来将控制权交还给调用者(协程上下文)。
非对称协程的执行始终由调用者主动开始或恢复,然后协程在合适的时机退出,交还控制权,调用者运行调度算法再来决定是否继续运行或结束协程。
协程的基本概念
promise_type
),在协程内部操控。协程通过这个对象提交结果或异常coroutine handle
),在协程外部操作。用于恢复协程执行或销毁协程帧coroutine state
),分配于堆上的内部对象,包含:
承诺对象
参数(copied by value)
挂起点
当前挂起点的局部变量和临时值,,状态信息和堆栈指针协程函数的执行流程
promise_type
对象。initial_suspend
:确定协程是否挂起。co_await
或 co_yield
。final_suspend
,决定如何结束和清理。协程函数需要一个特殊的返回值类型(以Result
为例),其中需要定义一个承诺对象(必须为Result::promise_type
的形式,且名为promise_type
),承诺对象需要实现一些必须的接口
#include <coroutine>
struct Result {
struct promise_type {
...
};
std::coroutine_handle<promise_type> handle;
};
// 或者
struct promise;
struct Result {
using promise_type = promise;
std::coroutine_handle<promise_type> handle;
};
struct promise {
...
};
co_await
和co_yield
可以挂起协程,co_return
退出协程
co_await
返回一个awaiter
对象,可以对挂起过程中的一些细节进行控制
co_yield
返回一个用户定义值,你可以在promise
对象中储存这个值,并使用handle.promise().value
来进行访问,例如Task的寄存器上下文
coroutine task() {
// do some tasks
co_await std::suspend_always {};
}
编译器会做的
corountine task() {
corourtine::promise_type p();
coroutine co_object = p.get_return_object();
try {
co_await p.initial_suspend();
// do some tasks
co_await std::suspend_always {};
} catch(...) {
p.unhandled_exception();
}
co_await p.final_suspend();
}
展开编译器的对于co_await
的实现
co_await std::suspend_always {};
// 之前构造的promise和coroutine object
corourtine::promise_type p();
coroutine co_object = p.get_return_object();
auto&& awaiter = std::suspend_always {};
if (!awaiter.await_ready()) {
awaiter.await_suspend(p.handle);
// leave here
}
// resume here
awaiter.resume();
awaiter
可以理解为在co_await
准备挂起协程时,提供了一个接口await_ready
,ready
指的是异步操作是否完成,来让你可以对异步操作的状态做一次检查,若异步操作在运行到此处时已经执行完成,则可以放弃挂起。这样避免了立即完成的异步操作会被直接挂起,需要等待下一次调度才能恢复
struct awaiter {
bool await_ready() {
return check_task_state();
}
};
协程在决定要挂起前,提供的一个接口,让你可以在挂起协程前做一些准备,例如,将协程句柄注册到回调、任务调度器或其他的一些异步管理机制中
总是挂起
void await_suspend(std::coroutine_handle<>)
返回true时挂起
bool await_suspend(std::coroutine_handle<>)
所以一个协程是否挂起需要 await_ready()
和 await_suspend()
的返回值共同决定。
await_ready()
:是第一步,用于决定是否直接跳过挂起过程。如果 await_ready()
返回 true
,表示异步任务已经准备好,协程不需要挂起,直接继续执行,不会调用 await_suspend()
。await_suspend()
:是第二步,如果 await_ready()
返回 false
,协程就会检查 await_suspend()
来决定是否挂起。await_suspend()
返回 true
时,协程挂起;如果返回 false
,协程会立即恢复,不会挂起。挂起并恢复返回的协程
std::coroutine_handle<> await_suspend(std::coroutine_handle<>)
使用coroutine_handle<>
可以表示任意协程句柄
在知道协程的类型时,promise
对象、协程句柄和raw address
可以进行转换
promise <> handle
handle = std::coroutine_handle<promise_type>::from_promise(promise);
promise = handle.promise();
handle <> raw address
address = handle.address();
handle = std::coroutine_handle<promise_type>::from_address(address);
协程句柄还提供在外部控制协程的一些接口
恢复协程
handle.resume();
检查协程完成状态
handle.done();
销毁句柄
handle.destroy();