**如何在C++20中使用协程实现异步迭代器?**

在C++20中,协程(coroutines)被引入为标准语言特性,允许我们在函数内部“挂起”和“恢复”,极大地方便了异步编程。本文将介绍如何利用协程实现一个通用的异步迭代器(async iterator),并演示其在实际项目中的应用场景。


1. 协程基础回顾

  • co_await:挂起协程,等待一个 awaitable 对象完成。
  • co_yield:挂起协程,返回一个值给调用者。
  • co_return:结束协程,返回一个值。
  • std::suspend_always / std::suspend_never:控制协程在何时挂起。

协程的实现细节由编译器完成,开发者只需关注 awaitable、generator 等高级抽象。


2. 定义协程返回类型

为了让协程返回一个可迭代的对象,我们需要实现 generator。其基本结构如下:

#include <coroutine>
#include <exception>
#include <iostream>
#include <string>
#include <vector>

// Forward declaration
template<typename T>
class generator;

// Promise type
template<typename T>
struct generator_promise {
    T current_value;
    std::exception_ptr current_exception = nullptr;

    // Initial suspend: never suspend
    std::suspend_always initial_suspend() { return {}; }

    // Final suspend: never suspend
    std::suspend_always final_suspend() noexcept { return {}; }

    // Yield value
    std::suspend_always yield_value(T value) {
        current_value = value;
        return {};
    }

    // Return void
    void return_void() {}

    // Exception handling
    void unhandled_exception() {
        current_exception = std::current_exception();
    }

    // Get the generator
    generator <T> get_return_object();
};

3. 生成器类

template<typename T>
class generator {
public:
    struct promise_type;
    using coro_handle = std::coroutine_handle <promise_type>;

    explicit generator(coro_handle h) : handle(h) {}
    generator(const generator&) = delete;
    generator& operator=(const generator&) = delete;
    generator(generator&& other) noexcept : handle(other.handle) { other.handle = nullptr; }
    generator& operator=(generator&& other) noexcept {
        if (this != &other) {
            if (handle) handle.destroy();
            handle = other.handle;
            other.handle = nullptr;
        }
        return *this;
    }
    ~generator() { if (handle) handle.destroy(); }

    // Iterator
    struct iterator {
        coro_handle coro;
        bool done = false;

        iterator(coro_handle h) : coro(h) {
            if (coro) {
                coro.resume();
                if (coro.done()) done = true;
            }
        }

        iterator& operator++() {
            if (coro) {
                coro.resume();
                if (coro.done()) done = true;
            }
            return *this;
        }

        const T& operator*() const { return coro.promise().current_value; }

        bool operator==(const iterator& other) const { return done == other.done; }
        bool operator!=(const iterator& other) const { return !(*this == other); }
    };

    iterator begin() { return iterator(handle); }
    iterator end()   { return iterator(); }

private:
    coro_handle handle;
};

实现 get_return_object

template<typename T>
generator <T> generator_promise<T>::get_return_object() {
    return generator <T>{coro_handle::from_promise(*this)};
}

4. 示例:异步读取文件行

下面演示如何利用协程实现异步读取文件行的迭代器。示例使用 std::ifstream,实际项目可替换为网络读取、数据库查询等异步源。

#include <fstream>
#include <optional>
#include <future>

generator<std::string> async_read_lines(const std::string& path) {
    std::ifstream file(path);
    if (!file.is_open()) co_return;

    std::string line;
    while (std::getline(file, line)) {
        // 模拟异步操作:在这里可以放置真正的 awaitable
        co_yield line;
    }
}

使用方式:

int main() {
    for (const auto& line : async_read_lines("sample.txt")) {
        std::cout << line << '\n';
    }
}

5. 性能与注意事项

  1. 协程生成器的开销

    • 每个 co_yield 产生一次挂起,涉及状态机的保存与恢复。若行数极多,频繁挂起可能导致性能下降。
    • 解决方案:批量读取(一次读取多行),在内部一次性 co_yield 产生一个 std::vector<std::string>
  2. 异常安全

    • 协程内部抛出的异常会被 unhandled_exception 捕获,并在外部使用 handle.promise().current_exception() 进行重新抛出。
  3. 线程安全

    • 协程本身不保证线程安全,若在多线程环境下使用,需自行同步或使用 std::asyncco_await 结合。

6. 进阶:与 std::future 结合

如果需要在异步迭代器中使用真正的异步 I/O(如网络请求),可以在协程内部 co_await 一个 std::future

#include <chrono>

generator <int> async_countdown(int start) {
    int value = start;
    while (value > 0) {
        // 异步等待1秒
        auto fut = std::async(std::launch::async, [value]{
            std::this_thread::sleep_for(std::chrono::seconds(1));
            return value;
        });
        value = co_await fut; // co_await std::future
        co_yield value;
    }
}

使用时:

for (int v : async_countdown(5))
    std::cout << v << " ";

7. 小结

  • 协程为 C++20 引入的强大异步编程机制。
  • 通过实现自定义 generator,可以轻松构建异步迭代器。
  • 在实际项目中,协程可配合异步 I/O、网络通信、数据库查询等,提升代码可读性与性能。
  • 注意协程的状态机开销、异常处理与线程安全问题。

希望本文能帮助你快速上手协程实现异步迭代器,为你的 C++ 项目注入新的活力!

发表评论