C++20 协程的实用技巧与最佳实践

在 C++20 中,协程(coroutines)正式成为语言的一部分,为异步编程和生成器提供了强大而灵活的工具。本文将从基本概念、实现细节、典型场景以及常见陷阱四个角度,帮助你快速掌握协程的实用技巧,并在实际项目中避免常见误区。

1. 协程的基本组成

协程函数与普通函数的差别在于它可以挂起(co_awaitco_yieldco_return)并在随后恢复执行。其核心关键字包括:

关键字 作用
co_await 等待一个 awaitable 对象,挂起协程
co_yield 生成一个值并挂起,类似生成器
co_return 结束协程并返回值,若没有值则相当于 void 返回
co_await 前的 awaitable 对象 必须满足 await_readyawait_suspendawait_resume 接口

协程的执行上下文(栈帧)由 Promise 对象持有,编译器会自动生成 awaiter 结构体。

2. 协程的实现细节

2.1 Promise 与 awaitable

struct AsyncTask {
    struct promise_type {
        int result_;
        std::suspend_always initial_suspend() { return {}; }
        std::suspend_always final_suspend() noexcept { return {}; }
        void return_value(int v) { result_ = v; }
        AsyncTask get_return_object() {
            return AsyncTask{std::coroutine_handle <promise_type>::from_promise(*this)};
        }
        void unhandled_exception() { std::rethrow_exception(std::current_exception()); }
    };

    std::coroutine_handle <promise_type> coro_;
    explicit AsyncTask(std::coroutine_handle <promise_type> h) : coro_(h) {}
    ~AsyncTask() { if (coro_) coro_.destroy(); }
    int get() { return coro_.promise().result_; }
};
  • initial_suspend() 控制协程在入口处是否挂起,final_suspend() 控制结束后挂起时间点。
  • return_value 存储返回结果,get_return_object 把 promise 转成用户可见的对象。

2.2 awaitable 的实现

struct TimerAwaitable {
    std::chrono::milliseconds wait_for;
    bool await_ready() const noexcept { return false; }
    void await_suspend(std::coroutine_handle<> h) {
        // 在异步事件循环中注册定时器
        event_loop::schedule(wait_for, [h](){ h.resume(); });
    }
    void await_resume() noexcept {}
};

await_ready 负责判断是否立即完成;await_suspend 负责挂起协程并安排恢复;await_resume 负责恢复时返回值。

3. 典型应用场景

3.1 异步 I/O

AsyncTask read_file(const std::string& path) {
    auto file = co_await async_open(path, O_RDONLY);
    char buffer[1024];
    while (true) {
        std::size_t n = co_await async_read(file, buffer, sizeof(buffer));
        if (n == 0) break;
        process(buffer, n);
    }
    co_return 0;
}

async_openasync_read 均返回 awaitable,协程在 I/O 完成前挂起,避免阻塞线程。

3.2 生成器

Generator <int> fibonacci(int n) {
    int a = 0, b = 1;
    for (int i = 0; i < n; ++i) {
        co_yield a;
        int t = a + b;
        a = b;
        b = t;
    }
}

`Generator

` 需要实现 `operator++` 与 `operator*`,协程内部使用 `co_yield` 产生每个值。 #### 3.3 事件循环与调度器 协程与事件循环(如 `asio::io_context`)配合,能将回调式代码转化为线性可读代码: “`cpp AsyncTask main() { std::string data = co_await fetch_from_network(“http://example.com”); std::cout

发表评论