使用C++20 协程实现异步文件读写

在 C++20 中引入的协程(coroutine)为编写高效、可读性更强的异步代码提供了强大工具。本文将从基础概念出发,展示如何利用协程实现异步文件读取与写入,并结合 std::filesystemstd::future 构建完整的异步 I/O 流程。通过示例代码,你可以快速把握协程的使用方式,并在实际项目中加以应用。

1. 协程基础回顾

协程是一种轻量级线程,能够在执行过程中挂起(co_await)并在需要时恢复。协程由三大概念组成:

  1. awaiter:负责提供挂起/恢复逻辑的对象。常见的有 std::futurestd::experimental::generator 等。
  2. promise:协程函数返回值的中介,负责在协程结束时提供结果。
  3. handle:协程的句柄,用来控制协程的生命周期(如 co_awaitresumedestroy)。

在异步 I/O 场景中,最常用的 awaiter 是 std::futurestd::experimental::task(第三方实现)。这里我们使用标准库提供的 std::futurestd::promise

2. 异步文件读取

2.1 设计思路

  • 读文件:在后台线程中读取文件内容,然后通过 std::promise 传递给主线程。
  • 协程:调用 co_await 等待 std::future 完成,并返回读取结果。

2.2 代码实现

#include <iostream>
#include <fstream>
#include <string>
#include <future>
#include <filesystem>
#include <coroutine>

namespace fs = std::filesystem;

// awaitable 类型,包装 std::future
template<typename T>
struct AwaitableFuture {
    std::future <T> 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) noexcept { std::thread([h]() { h.resume(); }).detach(); }
    T await_resume() { return fut.get(); }
};

// 异步读取文件
AwaitableFuture<std::string> async_read(const fs::path& path) {
    std::promise<std::string> prom;
    std::future<std::string> fut = prom.get_future();
    std::thread([p=std::move(prom), path](){ // 捕获 promise 并在线程中读取
        std::ifstream file(path, std::ios::binary);
        if (!file) {
            p.set_value(""); // 读取失败返回空串
            return;
        }
        std::string data((std::istreambuf_iterator <char>(file)), std::istreambuf_iterator<char>());
        p.set_value(std::move(data));
    }).detach();
    return AwaitableFuture<std::string>{std::move(fut)};
}

// 协程入口
struct AwaitableString {
    std::string value;
    AwaitableString(std::string&& v) : value(std::move(v)) {}
    std::string operator co_await() && { return std::move(value); }
};

AwaitableString read_file_co(const fs::path& path) {
    std::string data = co_await async_read(path);
    co_return std::move(data);
}

int main() {
    auto path = fs::current_path() / "sample.txt";
    // 写入一个示例文件
    std::ofstream(path) << "Hello, 协程世界!";

    // 通过协程读取文件
    std::string content = co_await read_file_co(path);
    std::cout << "文件内容: " << content << std::endl;

    return 0;
}

2.3 说明

  • async_read 在后台线程中完成文件读取,然后通过 std::promise 把结果写入 std::future
  • AwaitableFuture 是一个自定义 awaitable,满足协程的挂起/恢复要求。它在 await_suspend 中创建一个线程来恢复协程,确保主线程不会被阻塞。
  • read_file_co 是协程函数,内部 co_await async_read(path) 会挂起直到文件读取完成。

3. 异步文件写入

与读取类似,写入也可以使用协程包装。

AwaitableFuture <void> async_write(const fs::path& path, std::string data) {
    std::promise <void> prom;
    std::future <void> fut = prom.get_future();
    std::thread([p=std::move(prom), path, data=std::move(data)]() mutable {
        std::ofstream file(path, std::ios::binary);
        if (file) {
            file.write(data.c_str(), data.size());
        }
        p.set_value();
    }).detach();
    return AwaitableFuture <void>{std::move(fut)};
}

AwaitableString write_file_co(const fs::path& path, std::string data) {
    co_await async_write(path, std::move(data));
    co_return "写入完成";
}

4. 组合使用示例

int main() {
    auto path = fs::current_path() / "async.txt";
    std::string content = "C++20协程演示!";

    std::string write_res = co_await write_file_co(path, content);
    std::cout << write_res << std::endl;

    std::string read_res = co_await read_file_co(path);
    std::cout << "读取结果: " << read_res << std::endl;

    return 0;
}

5. 性能与注意事项

  • 线程池:示例使用 std::thread 的 detach,每次 I/O 调用都会产生一个线程,效率不高。实际项目中建议使用线程池(如 tbb::task_arenaboost::asio 或自定义线程池)来复用线程资源。
  • 错误处理:示例中错误情况直接返回空串或忽略错误。建议在 promise 里使用 set_exception 把异常传递给 future,协程中通过 try/catch 捕获。
  • 同步与异步的平衡:协程提供了易读的异步代码,但如果 I/O 主要是文件系统操作,操作系统已经提供了非阻塞 I/O(如 aio),可以与协程结合使用以获得更高效的 I/O。

6. 总结

通过上述代码,我们展示了如何使用 C++20 协程实现异步文件读写。关键点在于:

  1. std::promise/std::future 构建 awaitable。
  2. 在后台线程或线程池中完成 I/O。
  3. 在协程函数中使用 co_await 等待结果。

掌握了这些技巧后,你可以将异步 I/O 迁移到更高级的网络、数据库或 GUI 事件处理中,为 C++ 应用程序带来更高的并发性与更好的代码可维护性。

发表评论