使用C++20协程实现异步IO的简易示例

在C++20中,标准库加入了协程支持,提供了std::futurestd::promise等工具,甚至还有更轻量级的std::experimental::generator。下面给出一个完整的异步IO示例:我们使用协程从磁盘读取文件内容,并在读取完成后返回结果,而主线程可以在此期间做其他工作。示例演示了协程的基本语法、co_awaitco_return以及自定义awaitable类型的实现。代码分为三部分:文件读取的异步任务、简单的awaitable包装以及主程序。

#include <iostream>
#include <fstream>
#include <string>
#include <future>
#include <thread>
#include <chrono>
#include <coroutine>

// 简单的异步文件读取包装
struct FileReadAwaiter {
    std::string path;
    std::string result;
    bool done = false;

    // await_ready:如果立即完成返回true
    bool await_ready() const noexcept { return false; }

    // await_suspend:协程挂起,并在后台线程读取文件
    void await_suspend(std::coroutine_handle<> h) {
        std::thread([this, h]() {
            std::ifstream fin(path, std::ios::binary);
            if (!fin) { result = "文件打开失败"; }
            else {
                fin.seekg(0, std::ios::end);
                std::size_t size = fin.tellg();
                fin.seekg(0);
                result.resize(size);
                fin.read(&result[0], size);
            }
            done = true;
            h.resume(); // 读取完毕后恢复协程
        }).detach();
    }

    // await_resume:返回读取结果
    std::string await_resume() { return std::move(result); }
};

// 通过协程实现异步读取文件
std::future<std::string> asyncReadFile(const std::string& path) {
    struct Task {
        struct promise_type {
            std::promise<std::string> 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(std::string value) { prom.set_value(std::move(value)); }
            void unhandled_exception() { prom.set_exception(std::current_exception()); }
        };
        std::future<std::string> fut;
        Task(std::future<std::string> f) : fut(std::move(f)) {}
        std::future<std::string> get_future() { return std::move(fut); }
    };

    co_return co_await FileReadAwaiter{path};
}

// 主程序
int main() {
    std::cout << "开始异步读取文件...\n";
    auto fut = asyncReadFile("sample.txt");

    // 在这里可以做其他工作
    for (int i = 0; i < 5; ++i) {
        std::cout << "主线程工作中,循环 " << i+1 << "\n";
        std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }

    // 等待异步结果
    std::string content = fut.get();
    std::cout << "文件内容读取完成,长度: " << content.size() << " 字节\n";
    return 0;
}

关键点说明

  1. await_ready
    该函数判断协程是否可以立即完成。如果返回true,协程会直接执行await_resume而不挂起。这里返回false,始终挂起。

  2. await_suspend
    该函数接收当前协程句柄h,在这里我们创建一个后台线程完成文件读取。读取完成后通过h.resume()恢复协程。

  3. await_resume
    协程恢复后会调用此函数,返回最终结果。

  4. 协程返回
    asyncReadFile返回一个std::future<std::string>,在协程内部通过co_await得到读取结果,然后用co_return返回。

  5. 线程安全
    为了简单演示,后台线程直接detach。在生产代码中建议使用线程池或更安全的同步机制。

该示例演示了C++20协程与异步IO的结合,展示了如何在不阻塞主线程的情况下完成文件读取任务。通过适当封装,你可以将此模式扩展到网络请求、数据库查询等场景,从而实现高并发、低延迟的异步程序。

发表评论