**如何在 C++20 中使用协程实现异步文件读取?**

在现代 C++(尤其是 C++20)中,协程(coroutines)为实现非阻塞异步 I/O 提供了一种优雅的方式。本文将演示如何利用标准库(std::filesystemstd::experimental::coroutine 或者直接使用 <coroutine>)以及异步文件系统 API(例如 POSIX 的 aio_read 或 Windows 的 ReadFileEx)来构建一个简易的异步文件读取框架。

  1. 协程概念回顾

    • 协程是一种可暂停和恢复的函数。它在执行过程中可以挂起(co_awaitco_yield),并在外部事件完成后继续。
    • C++20 标准提供了 ` ` 头文件,定义了 `std::coroutine_handle`、`std::suspend_always`、`std::suspend_never` 等基础组件。
  2. 异步文件 I/O 的底层实现

    • POSIX 下可以使用 aio_read / aio_write
    • Windows 下可使用 ReadFileExReadFileScatter
    • 为了兼容性,这里使用 POSIX 的 aio_read 作为演示。
  3. 自定义 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);
        }
    };
  4. 异步读取函数

    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;
        });
    }
  5. 使用示例

    int main() {
        auto fut = async_read_file("example.txt");
        std::vector <char> content = fut.get(); // 阻塞直到协程完成
        std::cout << "文件内容长度: " << content.size() << "\n";
        return 0;
    }
  6. 进一步优化

    • 错误处理:在 await_resume 中返回 std::expected 或自定义错误类型。
    • 多文件并行:使用 std::when_all(C++23)或手动等待多个 await_suspend
    • 事件循环:将 aio_suspend 替换为 poll / epoll,实现更高效的事件驱动模型。
  7. 总结
    通过协程的 co_await 机制,我们可以将传统的回调式异步 I/O 写成看似同步的代码,极大提升代码可读性与维护性。C++20 的协程框架为构建高性能网络或文件系统服务奠定了基础,后续可以结合 std::spanstd::format 等现代特性进一步简化实现。

发表评论