c++20引入的协程特性为异步编程提供了更简洁的语法支持,co_await作为协程中用于挂起当前协程并等待操作完成的关键字,其背后依赖的是awaiter对象的实现。自定义co_await表达式本质上就是实现一个符合awaiter接口要求的类型,让该类型的实例能够被co_await正确调用。

awaiter的核心接口要求
一个类型要成为合法的awaiter,必须实现三个特定的成员函数,这三个函数分别对应协程挂起、等待完成判断、恢复执行三个阶段的逻辑:
- await_ready:返回bool类型,用于判断是否需要挂起协程。如果返回true,说明等待的操作已经就绪,协程不需要挂起,直接继续执行后续逻辑;如果返回false,协程将被挂起。
- await_suspend:接受一个协程句柄作为参数,在协程挂起后调用。可以在该函数中执行注册回调、保存协程句柄等操作,返回值可以是bool、void或者协程句柄,用于控制协程挂起后的行为。
- await_resume:在协程恢复执行后调用,返回的值就是整个co_await表达式的结果。可以在该函数中返回等待操作的最终结果,或者处理等待完成后的收尾逻辑。
自定义简单awaiter实现示例
下面实现一个简单的延时awaiter,当co_await该awaiter时,协程会挂起指定的时间后再恢复执行,首先定义awaiter类型:
#include <coroutine>
#include <thread>
#include <chrono>
#include <iostream>
// 自定义延时awaiter类型
struct DelayAwaiter {
// 延时时长,单位毫秒
int delay_ms;
// 构造函数,传入需要延时的毫秒数
explicit DelayAwaiter(int ms) : delay_ms(ms) {}
// 判断是否需要挂起,这里默认返回false,即总是挂起协程等待延时完成
bool await_ready() const noexcept {
return false;
}
// 协程挂起后执行的逻辑,这里启动一个线程延时后恢复协程
void await_suspend(std::coroutine_handle<> handle) const noexcept {
// 启动新线程执行延时逻辑
std::thread([handle, this]() {
// 睡眠指定时长
std::this_thread::sleep_for(std::chrono::milliseconds(delay_ms));
// 延时完成,恢复协程执行
handle.resume();
}).detach();
}
// 协程恢复后执行的逻辑,这里返回void,也可以返回需要的结果
void await_resume() const noexcept {}
};
配合协程使用自定义awaiter
定义好awaiter之后,需要在协程函数中使用co_await调用它,首先定义一个简单的协程返回类型,用于支持协程的基本运行:
// 简单的协程返回类型,仅支持基本协程逻辑
struct SimpleTask {
struct promise_type {
SimpleTask get_return_object() { return SimpleTask{}; }
std::coroutine_handle<> initial_suspend() { return std::suspend_never{}; }
std::coroutine_handle<> final_suspend() noexcept { return std::suspend_never{}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
// 协程函数,使用自定义的DelayAwaiter
SimpleTask test_coroutine() {
std::cout << "协程开始执行" << std::endl;
// co_await自定义的延时awaiter,挂起1秒
co_await DelayAwaiter(1000);
std::cout << "协程恢复执行,延时完成" << std::endl;
}
在main函数中调用该协程,可以看到输出结果符合预期:
int main() {
// 启动协程
test_coroutine();
// 主线程等待足够时间,避免协程未执行完程序就退出
std::this_thread::sleep_for(std::chrono::seconds(2));
return 0;
}
awaiter的进阶实现注意事项
在实际开发中,自定义awaiter还需要注意几个问题:
异常安全处理
await_suspend函数中如果可能抛出异常,需要做好异常处理,避免异常传递到协程外部导致程序崩溃。如果await_suspend返回bool类型,返回false的话协程不会挂起,会直接继续执行后续逻辑。
协程句柄的生命周期管理
在await_suspend中保存协程句柄时,要确保在协程恢复之前句柄是有效的,不能保存已经销毁的协程的句柄,否则调用resume会导致未定义行为。
返回值的处理
await_resume的返回类型就是co_await表达式的类型,如果需要返回等待的结果,比如异步读取的数据,可以在await_resume中返回对应的值,协程中co_await的接收变量就可以拿到这个结果。
通过上述步骤,就可以根据自己的需求自定义不同的awaiter,实现各类异步等待场景的co_await表达式,充分发挥c++协程的异步编程优势。