在 C++20 中,协程已成为语言的一部分,但默认的 co_await 只支持 std::suspend_always、std::suspend_never、以及符合 Awaitable 协议的对象。若想在协程中使用自定义的等待逻辑(比如基于事件驱动的任务调度器、异步 IO 等),就需要实现自己的 awaiter。本文将详细讲解如何实现一个自定义 awaiter,包含基本接口、状态管理以及与协程句柄的交互。
1. Awaitable 的基本要求
要让一个对象可 co_await,它必须满足以下成员:
| 成员 | 说明 | 返回类型 |
|---|---|---|
bool await_ready() |
判断是否需要挂起 | bool |
void await_suspend(std::coroutine_handle<>) |
挂起时的操作 | void 或 bool(若返回 true 则挂起,false 直接继续) |
decltype(auto) await_resume() |
协程恢复后返回值 | 任意 |
此外,协程句柄类型取决于协程的返回类型。若返回 void,则使用 std::coroutine_handle<>;若返回 T,则使用 std::coroutine_handle<promise_type>。
2. 设计自定义 Awaiter
假设我们要实现一个基于事件循环的 TimerAwaiter,在指定时间后恢复协程。下面是基本结构:
#include <chrono>
#include <coroutine>
#include <functional>
#include <unordered_set>
template <typename Clock = std::chrono::steady_clock>
class TimerAwaiter {
public:
using duration = typename Clock::duration;
explicit TimerAwaiter(duration d) : delay_(d) {}
bool await_ready() const noexcept { return delay_ == duration::zero(); }
void await_suspend(std::coroutine_handle<> h) {
// 将协程句柄与超时时间一起加入全局事件循环
get_event_loop().schedule(h, Clock::now() + delay_);
}
void await_resume() const noexcept { /* 无返回值 */ }
private:
duration delay_;
// 简单事件循环(单线程)
static struct EventLoop {
// map 事件时间 -> 句柄列表
std::multimap<Clock::time_point, std::coroutine_handle<>> timers_;
std::unordered_set<std::coroutine_handle<>> active_;
void schedule(std::coroutine_handle<> h, Clock::time_point when) {
timers_.emplace(when, h);
active_.insert(h);
}
// 每帧或在单独线程中调用
void tick() {
auto now = Clock::now();
auto it = timers_.begin();
while (it != timers_.end() && it->first <= now) {
it->second.resume(); // 恢复协程
active_.erase(it->second); // 移除
it = timers_.erase(it); // 删除计时器
}
}
} & get_event_loop() {
static EventLoop loop;
return loop;
}
};
说明:
await_ready()在延迟为 0 时直接完成,避免无用挂起。await_suspend()将协程句柄h与未来的触发时间一起注册到事件循环。- 事件循环的
tick()在主线程或单独线程中周期性调用,用来检查已到时间的协程并恢复。
3. 在协程中使用
#include <iostream>
#include <chrono>
struct Sleep {
std::chrono::milliseconds ms;
};
Sleep operator"" _ms(unsigned long long v) {
return Sleep{std::chrono::milliseconds(static_cast<std::chrono::milliseconds::rep>(v))};
}
auto worker() -> std::generator <void> { // C++20 std::generator (C++23 以后)
std::cout << "Start\n";
co_await TimerAwaiter<std::chrono::steady_clock>(1000_ms);
std::cout << "After 1s\n";
co_await TimerAwaiter<std::chrono::steady_clock>(500_ms);
std::cout << "After 0.5s\n";
}
int main() {
auto gen = worker();
while (gen) {
gen.resume(); // 手动驱动
TimerAwaiter<>:get_event_loop().tick(); // 触发事件
}
}
此示例演示了如何在协程中使用自定义 awaiter。TimerAwaiter 可以替换为任何符合 Awaitable 接口的对象,例如基于 I/O 的 AsyncReadAwaiter、网络请求的 HttpAwaiter 等。
4. 与 Promise 结合
若协程返回值非 void,则需要实现 promise_type。下面给出一个 `Task