C++20 协程:实现异步 I/O 的简易框架

在 C++20 里,协程(coroutine)被正式纳入标准,提供了 co_awaitco_yieldco_return 等关键字,使得异步编程可以像同步代码一样书写。下面我们用 C++20 协程来实现一个简易的异步 I/O 框架,演示如何利用 std::futurestd::promise 与协程共同完成网络请求或文件读取等任务。


1. 先决条件

  • 编译器:支持 C++20 并已开启协程支持,例如 g++-13 -std=c++20 -fcoroutinesclang++-16 -std=c++20 -fcoroutines-ts
  • 依赖库:本文仅使用标准库,无需额外依赖。

2. 协程与未来的基本关系

协程的执行体可以暂停(co_await)并在事件完成后继续。我们将定义一个 `Task

` 类型,它内部封装了 `std::future`,并通过 `co_await` 与事件源交互。 “`cpp #include #include #include #include #include #include template struct Task { struct promise_type { std::promise prom; Task get_return_object() { return Task{prom.get_future()}; } std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_value(T value) noexcept { prom.set_value(value); } void unhandled_exception() noexcept { prom.set_exception(std::current_exception()); } }; std::future fut; Task(std::future f) : fut(std::move(f)) {} Task(const Task&) = delete; Task(Task&&) = default; }; “` > 这里 `promise_type` 的 `initial_suspend` 与 `final_suspend` 均为 `suspend_never`,意味着协程立即开始执行并在完成时结束。 — ## 3. 模拟异步 I/O 为了演示,假设我们有一个异步读取文件的接口 `async_read_file`,它返回 `Task<std::vector>`。真正的异步 I/O 需要系统调用或第三方库,但我们使用 `std::thread` 模拟延迟。 “`cpp Task<std::vector> async_read_file(const std::string& path) { std::promise<std::vector> prom; auto fut = prom.get_future(); std::thread([path, prom = std::move(prom)]() mutable { // 模拟 I/O 延迟 std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 读取内容(这里仅返回 dummy 数据) std::vector data = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘C++’, ‘!’}; prom.set_value(std::move(data)); }).detach(); // 让协程等待 future 完成 co_await std::suspend_always{}; // 当事件触发后,future 已 ready,可以获取结果 co_return fut.get(); } “` > `co_await std::suspend_always{}` 用于让协程暂停,等到 `fut` 可获取结果后才继续。实际项目中可用 `co_await` 结合自定义 awaiter。 — ## 4. 组合协程实现更复杂逻辑 下面演示如何将多个异步读取串联起来,形成流水线: “`cpp Task process_files(const std::vector& paths) { int total = 0; for (const auto& p : paths) { auto data = co_await async_read_file(p); // 等待文件读取完成 total += static_cast (data.size()); // 简单统计字节数 } co_return total; // 返回所有文件的总字节数 } “` 在主函数中启动协程并等待结果: “`cpp int main() { std::vector files = {“a.txt”, “b.txt”, “c.txt”}; auto task = process_files(files); std::cout << "Total bytes: " << task.fut.get() << std::endl; } “` — ## 5. 进一步改进:使用 Awaiter 上面示例中使用了 `co_await std::suspend_always{}`,这不够灵活。下面给出一个 `FutureAwaiter`,可以直接 `co_await` 一个 `std::future`。 “`cpp template struct FutureAwaiter { std::future & fut; bool await_ready() const noexcept { return fut.wait_for(std::chrono::seconds(0)) == std::future_status::ready; } void await_suspend(std::coroutine_handle h) { std::thread([this, h](){ fut.wait(); h.resume(); }).detach(); } R await_resume() { return fut.get(); } }; template FutureAwaiter operator co_await(std::future& f) { return FutureAwaiter {f}; } “` 现在 `async_read_file` 可以简化为: “`cpp Task<std::vector> async_read_file(const std::string& path) { std::promise<std::vector> prom; auto fut = prom.get_future(); std::thread([path, prom = std::move(prom)]() mutable { std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::vector data = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘C++’, ‘!’}; prom.set_value(std::move(data)); }).detach(); co_return co_await fut; // 直接等待 future 完成 } “` — ## 6. 小结 – C++20 协程提供了极简的语法,让异步逻辑像同步代码一样可读。 – 通过自定义 `Task ` 和 `FutureAwaiter`,我们可以轻松把 `std::future` 与协程整合。 – 本示例使用 `std::thread` 模拟异步 I/O,实际项目中可替换为网络、文件或数据库的真正异步调用。 从此,你可以在 C++20 项目中更自如地使用协程,写出既优雅又高性能的异步代码。祝编码愉快!</std::vector</std::vector</std::vector</std::vector</std::vector

发表评论