C++20中的协程:如何在异步IO中使用`co_await`?

在C++20中,协程(coroutine)为异步编程提供了语言级支持。co_awaitco_yieldco_return让我们能够像编写同步代码一样写出异步逻辑。本文将演示如何在网络I/O中使用协程,并解释背后的关键概念。

1. 基本概念

  • promise_type:每个协程都有一个关联的promise_type,它负责创建协程句柄、存储返回值、处理异常等。
  • awaitable:实现await_ready(), await_suspend(), await_resume()接口的对象,表示可以被co_await的对象。
  • co_await:在执行到co_await时,协程会挂起,直到awaitable完成。

2. 简单示例:文件读取

假设我们有一个异步文件读取接口async_read_file,返回一个awaitable<std::string>。下面是一个使用协程读取文件并打印内容的例子:

#include <iostream>
#include <string>
#include <future>
#include <thread>
#include <chrono>

struct AwaitableRead {
    std::string file_path;
    std::promise<std::string> prom;

    AwaitableRead(const std::string& path) : file_path(path) {}

    bool await_ready() noexcept { return false; }

    void await_suspend(std::coroutine_handle<> h) {
        // 异步读取模拟:在后台线程读取文件
        std::thread([this, h]() {
            std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 模拟延迟
            // 这里可以放置真正的文件读取逻辑
            prom.set_value("文件内容:" + file_path);
            h.resume(); // 恢复协程
        }).detach();
    }

    std::string await_resume() { return prom.get_future().get(); }
};

AwaitableRead async_read_file(const std::string& path) {
    return AwaitableRead(path);
}

auto read_and_print() -> std::future <void> {
    std::string content = co_await async_read_file("example.txt");
    std::cout << content << std::endl;
}

3. 处理异常

如果读取过程中抛出异常,可以在await_suspend里捕获并传递给promise:

void await_suspend(std::coroutine_handle<> h) {
    std::thread([this, h]() {
        try {
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
            // 假设读取失败
            throw std::runtime_error("读取错误");
            prom.set_value("...");
        } catch (...) {
            prom.set_exception(std::current_exception());
        }
        h.resume();
    }).detach();
}

4. 与std::future的整合

C++20标准库提供std::future与协程的交互方式。我们可以用co_return返回值给std::future

auto async_operation() -> std::future <int> {
    co_return 42; // 直接返回给future
}

5. 性能考虑

  • 协程的状态机生成开销很小,通常不超过几十字节。
  • await_suspendawait_resume的调用链会产生函数调用开销,但相较于传统回调式,简洁且易于维护。
  • 需要注意:如果在协程中使用阻塞操作,会导致线程阻塞,失去协程非阻塞的优势。

6. 总结

C++20协程通过co_await简化了异步代码的书写,使得异步逻辑与同步代码保持一致。通过自定义awaitable对象,可以将任何异步操作(如网络、文件、定时器)包装成协程接口。掌握这些核心概念后,你就可以在现代C++项目中轻松实现高性能、可维护的异步系统。

祝你编码愉快,C++协程之旅顺利开启!

发表评论