C++20 中的协程:如何实现异步任务调度?

在 C++20 里,协程(coroutines)成为了标准库的一部分,为异步编程提供了更简洁、直观的语法。协程本质上是能够“暂停”和“恢复”的函数,结合任务调度器(scheduler)可以实现高效的异步任务执行。下面我们详细探讨协程的基本语义、实现细节以及如何利用标准库中的 std::generatorstd::task 来完成异步任务调度。

1. 协程的基本语义

1.1 co_awaitco_yieldco_return

  • co_await:在协程内部暂停,等待某个 awaitable(可等待对象)完成后再恢复。返回值会被作为 await 表达式的结果。
  • co_yield:在生成器(generator)中使用,向调用者返回一个值,并暂停协程;下次调用时从此处恢复。
  • co_return:结束协程,返回最终结果。

1.2 协程的生命周期

协程在第一次调用时会生成一个“协程对象”,其内部保存了状态机的栈帧和协程句柄(std::coroutine_handle<>)。协程的状态(Suspended、Ready、Executing、Completed)由编译器自动管理。

2. 标准库中的协程类型

2.1 std::generator

#include <experimental/generator>

std::experimental::generator <int> range(int start, int end) {
    for (int i = start; i < end; ++i) {
        co_yield i;
    }
}

std::generator 让我们能像遍历容器一样使用协程产生的值。

2.2 std::task

#include <experimental/task>

std::experimental::task <int> async_add(int a, int b) {
    co_return a + b;
}

std::task 表示一个异步结果,常与协程调度器配合使用。

3. 任务调度器实现

一个简单的协程调度器可以是一个事件循环,将待执行的协程封装成 std::function<void()>,并放入队列中。下面给出一个最简实现:

#include <queue>
#include <functional>
#include <coroutine>

class SimpleScheduler {
public:
    void schedule(std::coroutine_handle<> coro) {
        tasks.emplace([coro](){ coro.resume(); });
    }

    void run() {
        while (!tasks.empty()) {
            auto task = std::move(tasks.front());
            tasks.pop();
            task();
        }
    }

private:
    std::queue<std::function<void()>> tasks;
};

在协程中,当遇到 co_await 时,awaitable 可以在其 await_suspend 方法中将协程句柄交给调度器,等待完成后再恢复。

4. 组合示例:异步文件读取

假设我们要实现一个异步文件读取,返回文件内容字符串。下面演示如何结合 std::task 与调度器实现:

#include <iostream>
#include <fstream>
#include <experimental/task>
#include <experimental/coroutine>

class FileReadAwaitable {
public:
    FileReadAwaitable(const std::string& path) : file_path(path) {}

    bool await_ready() const noexcept { return false; }

    void await_suspend(std::coroutine_handle<> h) {
        // 这里使用简单的同步读取,实际可换成 I/O 线程或异步 I/O
        scheduler.schedule(h);
    }

    std::string await_resume() {
        std::ifstream file(file_path);
        std::string content((std::istreambuf_iterator <char>(file)),
                             std::istreambuf_iterator <char>());
        return content;
    }

private:
    std::string file_path;
};

std::experimental::task<std::string> async_read_file(const std::string& path) {
    std::string data = co_await FileReadAwaitable(path);
    co_return data;
}

int main() {
    SimpleScheduler scheduler;
    auto read_task = async_read_file("example.txt");

    // 将协程句柄放入调度器
    scheduler.schedule(read_task.holder().promise().get_handle());

    scheduler.run();

    std::cout << "File content:\n" << read_task.result() << std::endl;
    return 0;
}

说明

  • FileReadAwaitableawait_suspend 中把协程句柄交给调度器,调度器随后会在事件循环中恢复协程。
  • async_read_file 通过 co_await 调用 awaitable,获取文件内容后返回。

5. 小结

C++20 的协程为异步编程提供了强大而灵活的基础设施。通过 std::generatorstd::task,配合自定义的调度器,你可以在不依赖第三方框架的情况下实现高性能的异步任务调度。理解协程的生命周期与 awaitable 的实现细节是关键,掌握后可进一步探索如网络 I/O、定时器、并发任务池等更复杂的场景。

发表评论