1. 认识 C20 协程C20 引入的协程是“无栈协程”它允许函数在执行过程中暂停并在之后恢复执行而无需阻塞线程。与传统的回调或状态机相比协程能让异步代码看起来像同步调用极大提升可读性和可维护性。协程的核心由三个关键词构成co_await用于挂起并等待结果co_yield用于挂起并返回一个值通常用于生成器co_return则用来结束协程并最终返回值。2. 协程的核心组件一个 C 协程在编译时会被转化为一个状态机编译器要求开发者提供几个关键对象promise_type协程与调用者的通信桥梁定义在返回类型内部。它负责协程的创建、挂起时的控制以及最终结果和异常的传递。awaiterco_await 表达式控制挂起/恢复逻辑。通过实现await_ready、await_suspend和await_resume三个方法可以决定是否立即挂起、挂起后做什么以及恢复后返回什么值。coroutine_handle一个轻量级句柄用于从外部恢复或销毁协程如handle.resume()或handle.destroy()。下面是一个最简协程返回类型和 promise_type 的定义示例展示了编译器如何将协程体的逻辑挂接起来。#include coroutine #include iostream struct Task { struct promise_type { Task get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; };3. 第一个协程从 co_await 开始我们定义一个自定义 Awaitable它会在被 co_await 时打印信息并模拟异步等待。在协程内部调用co_await即可挂起当条件满足时恢复执行。#include coroutine #include iostream #include thread #include chrono struct AwaitableDemo { bool await_ready() { return false; } // 总是挂起 void await_suspend(std::coroutine_handle h) { std::cout 协程挂起模拟异步操作...\n; std::thread([h]() { std::this_thread::sleep_for(std::chrono::seconds(1)); std::cout 异步操作完成恢复协程\n; h.resume(); }).detach(); } int await_resume() { std::cout 协程恢复执行\n; return 42; } }; struct MyTask { struct promise_type { MyTask get_return_object() { return {}; } std::suspend_never initial_suspend() { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() {} void unhandled_exception() {} }; }; MyTask demo_coroutine() { std::cout 协程开始\n; int result co_await AwaitableDemo{}; std::cout 获取结果: result \n; std::cout 协程结束\n; } int main() { std::cout 主函数启动协程\n; demo_coroutine(); std::cout 主函数继续执行其他工作\n; std::this_thread::sleep_for(std::chrono::seconds(2)); // 等待协程完成 return 0; }运行后你会发现主函数不会阻塞在协程调用上协程在co_await处挂起主线程继续往下走一秒钟后协程被恢复打印结果并结束。这就是协程“可暂停函数”的魅力。4. 使用 co_yield 构建生成器co_yield可以让协程像 Python 的生成器一样每次暂停时向外产出一个值。下面实现一个生成斐波那契数列的生成器演示如何定义 generator 类型及其对应的 promise_type。#include coroutine #include iostream #include optional templatetypename T struct Generator { struct promise_type { std::optionalT current_value; Generator get_return_object() { return Generator{ std::coroutine_handlepromise_type::from_promise(*this) }; } std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } std::suspend_always yield_value(T value) { current_value value; return {}; } void return_void() {} void unhandled_exception() { std::exit(1); } }; std::coroutine_handlepromise_type handle; explicit Generator(std::coroutine_handlepromise_type h) : handle(h) {} ~Generator() { if (handle) handle.destroy(); } bool move_next() { if (!handle.done()) { handle.resume(); } return !handle.done(); } T current_value() const { return handle.promise().current_value.value(); } }; Generatorint fibonacci(int n) { int a 0, b 1; for (int i 0; i n; i) { co_yield a; auto next a b; a b; b next; } } int main() { auto gen fibonacci(10); while (gen.move_next()) { std::cout gen.current_value() ; } return 0; }5. 实战要点与常见坑在实际项目中使用协程时有几个要点需要特别注意生命周期管理协程帧内捕获的引用或指针必须保证在协程恢复时仍然有效避免悬垂引用。建议使用std::shared_ptr延长依赖对象的生存期或者将参数按值传递给协程。对称转移在await_suspend中直接返回另一个协程的句柄可以实现“对称转移”将当前协程挂起并直接恢复下一个协程避免逐级返回带来的开销。例如std::coroutine_handle await_suspend(std::coroutine_handle h) { return next_handle; }。final_suspend如果想要在外部安全地销毁协程final_suspend 应该返回std::suspend_always这样协程结束后不会自动销毁自己的帧方便调用者检查完成状态后再手动销毁。异常处理协程内抛出的异常会被捕获并传递给promise_type::unhandled_exception()在这里可以通过std::current_exception()重新抛出并存储或直接调用std::terminate。6. 总结与推荐资源C20 协程提供了一种强大的异步编程工具但它的基础设施promise_type、awaiter需要手动实现这也是上手门槛所在。建议初学者从简单的co_await示例入手逐步理解挂起/恢复机制再尝试封装自己的 Task 和 Generator 类型。在实际工程中可以结合现有异步框架如 Boost.Asio 的协程适配来避免重复造轮子。推荐进一步阅读 C 标准提案 N4849 中关于协程的部分以及 Lewis Baker 的经典博客“C Coroutines: Understanding operator co_await”、“C Coroutines: Understanding Symmetric Transfer”这些是深入理解协程内部机制的绝佳资源。