在C++20中,协程(coroutine)被正式纳入标准库,为异步编程提供了更简洁、更直观的语法。本文将演示如何利用C++20协程实现一个异步文件读取器,并解释其工作原理与关键点。
1. 先决条件
- 编译器支持C++20协程:如 GCC 11+, Clang 13+, MSVC 19.29+。
- 标准库包含 ` `,文件 I/O 用 “。
2. 设计思路
我们需要一个能返回 std::future 或自定义 Awaitable 的协程。下面的实现采用 std::future 与 std::promise 的组合,完成:
- 异步读取器:
async_read_file(const std::string& path),返回std::future<std::string>。 - 协程主体:使用
co_await等待 I/O 完成后,将结果传递给co_return。
虽然标准库并未提供真正的异步文件 I/O,下面的示例通过 std::async 模拟后台线程完成 I/O,然后协程等待其结果。
3. 代码实现
#include <iostream>
#include <fstream>
#include <string>
#include <future>
#include <coroutine>
#include <exception>
// ---------- 简易 Awaitable ----------
struct AsyncFileAwaitable {
std::future<std::string> fut;
explicit AsyncFileAwaitable(std::future<std::string> f) : fut(std::move(f)) {}
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([h, f = std::move(fut)]() mutable {
// 让后台线程等待 I/O 完成
f.wait();
// I/O 完成后恢复协程
h.resume();
}).detach();
}
std::string await_resume() { return fut.get(); }
};
// ---------- 异步读取函数 ----------
AsyncFileAwaitable async_read_file(const std::string& path) {
// 通过 std::async 模拟后台 I/O
auto fut = std::async(std::launch::async, [path]() -> std::string {
std::ifstream file(path, std::ios::binary);
if (!file) throw std::runtime_error("Cannot open file: " + path);
std::string content((std::istreambuf_iterator <char>(file)),
std::istreambuf_iterator <char>());
return content;
});
return AsyncFileAwaitable(std::move(fut));
}
// ---------- 协程入口 ----------
struct AsyncFileReader {
struct promise_type {
AsyncFileReader get_return_object() { return {}; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
void read_file_task(const std::string& path) {
AsyncFileReader coro; // 协程句柄占位
std::string content = co_await async_read_file(path);
std::cout << "文件内容(长度 " << content.size() << " 字节)已读取。\n";
// 进一步处理 content ...
}
// ---------- 主程序 ----------
int main() {
std::string file_path = "example.txt";
try {
read_file_task(file_path); // 启动协程
} catch (const std::exception& e) {
std::cerr << "错误: " << e.what() << '\n';
}
// 为了让后台线程有时间完成,简单延迟
std::this_thread::sleep_for(std::chrono::seconds(1));
return 0;
}
4. 关键点说明
-
Awaitable 结构
await_ready判断是否已完成。await_suspend将协程挂起,并在后台线程完成 I/O 后恢复协程。await_resume在协程恢复时获取结果。
-
async_read_file- 利用
std::async将同步文件读取包装为异步任务。 - 返回自定义
AsyncFileAwaitable,实现与协程的无缝交互。
- 利用
-
协程入口
- 由于我们只需要演示异步读取,
AsyncFileReader仅提供promise_type的最小实现。 - 在
read_file_task中使用co_await等待文件读取完成。
- 由于我们只需要演示异步读取,
-
后台线程与协程的配合
await_suspend通过std::thread启动后台工作。detach()保证后台线程不阻塞主线程。- 当 I/O 结束后
h.resume()恢复协程。
5. 性能与实际应用
- 该实现仅为演示;在真实项目中,应使用专门的异步 I/O 库(如 Boost.Asio、libuv 等)或操作系统原生异步 API(如 Linux 的
io_uring)。 - C++20 协程仅提供语言层面的语法支持,具体异步机制仍需库或系统支持。
- 若目标是高并发文件读取,建议将 I/O 与协程结合使用,并配合线程池或事件循环。
6. 小结
本文通过 async_read_file 与自定义 Awaitable,展示了如何在 C++20 协程中实现异步文件读取。虽然使用了 std::async 模拟后台线程,但核心思路可以迁移到真正的异步 I/O 框架中,进一步提升性能与可扩展性。掌握这一模式后,你可以轻松在 C++20 项目中加入高效的异步 I/O 逻辑。