在现代 C++(尤其是 C++20)中,协程(coroutines)为实现非阻塞异步 I/O 提供了一种优雅的方式。本文将演示如何利用标准库(std::filesystem、std::experimental::coroutine 或者直接使用 <coroutine>)以及异步文件系统 API(例如 POSIX 的 aio_read 或 Windows 的 ReadFileEx)来构建一个简易的异步文件读取框架。
-
协程概念回顾
- 协程是一种可暂停和恢复的函数。它在执行过程中可以挂起(
co_await、co_yield),并在外部事件完成后继续。 - C++20 标准提供了 ` ` 头文件,定义了 `std::coroutine_handle`、`std::suspend_always`、`std::suspend_never` 等基础组件。
- 协程是一种可暂停和恢复的函数。它在执行过程中可以挂起(
-
异步文件 I/O 的底层实现
- POSIX 下可以使用
aio_read/aio_write。 - Windows 下可使用
ReadFileEx或ReadFileScatter。 - 为了兼容性,这里使用 POSIX 的
aio_read作为演示。
- POSIX 下可以使用
-
自定义 Awaitable 对象
#include <coroutine> #include <aio.h> #include <unistd.h> #include <sys/stat.h> #include <fcntl.h> #include <iostream> #include <vector> #include <memory> #include <cstring> struct aio_result { struct aiocb cb; std::size_t read_size; std::vector <char> buffer; }; struct aio_read_awaiter { aio_result* res; aio_read_awaiter(aio_result* r) : res(r) {} bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle<> h) noexcept { // 提交异步读请求 res->buffer.resize(res->read_size); res->cb.aio_buf = res->buffer.data(); res->cb.aio_nbytes = res->read_size; res->cb.aio_fildes = res->cb.aio_fildes; // 预先设置 int err = aio_read(&res->cb); if (err) { std::cerr << "aio_read error: " << std::strerror(errno) << "\n"; h.resume(); // 立即恢复 } } std::vector <char> await_resume() noexcept { // 等待完成 while (aio_error(&res->cb) == EINPROGRESS) { aio_suspend(&res->cb, 1, nullptr); } if (aio_error(&res->cb) != 0) { std::cerr << "aio_error: " << std::strerror(aio_error(&res->cb)) << "\n"; return {}; } return std::move(res->buffer); } }; -
异步读取函数
std::future<std::vector<char>> async_read_file(const std::string& path) { return std::async(std::launch::async, [path]() -> std::vector <char> { int fd = open(path.c_str(), O_RDONLY); if (fd < 0) throw std::runtime_error("open failed"); struct stat st{}; fstat(fd, &st); std::size_t size = st.st_size; aio_result res; res.cb.aio_fildes = fd; res.read_size = size; aio_read_awaiter awaiter(&res); std::vector <char> data = co_await awaiter; // 协程挂起 close(fd); co_return data; }); } -
使用示例
int main() { auto fut = async_read_file("example.txt"); std::vector <char> content = fut.get(); // 阻塞直到协程完成 std::cout << "文件内容长度: " << content.size() << "\n"; return 0; } -
进一步优化
- 错误处理:在
await_resume中返回std::expected或自定义错误类型。 - 多文件并行:使用
std::when_all(C++23)或手动等待多个await_suspend。 - 事件循环:将
aio_suspend替换为poll/epoll,实现更高效的事件驱动模型。
- 错误处理:在
-
总结
通过协程的co_await机制,我们可以将传统的回调式异步 I/O 写成看似同步的代码,极大提升代码可读性与维护性。C++20 的协程框架为构建高性能网络或文件系统服务奠定了基础,后续可以结合std::span、std::format等现代特性进一步简化实现。