C++20 引入了协程(Coroutine)的原语,使得在函数内部可以“挂起”(suspend)和“恢复”(resume)执行,而不需要手动管理状态机。下面给出一个完整的示例,展示如何实现一个简单的异步数据流,并用协程来生成和消费数据。
1. 基础概念
- Generator:一个可迭代的对象,内部使用
co_yield生成值。 - Task:一个可以异步执行的函数,使用
co_return返回结果。 - Awaitable:任何可被
co_await的对象,例如std::future、std::promise、自定义等待器。
2. 一个简易的 Generator 实现
#include <coroutine>
#include <exception>
#include <iostream>
#include <vector>
template<typename T>
struct Generator {
struct promise_type {
T current_value;
std::exception_ptr exception;
Generator get_return_object() {
return Generator{
.handle = std::coroutine_handle <promise_type>::from_promise(*this)
};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
std::suspend_always yield_value(T value) {
current_value = value;
return {};
}
void unhandled_exception() { exception = std::current_exception(); }
void return_void() {}
};
std::coroutine_handle <promise_type> handle;
~Generator() { if (handle) handle.destroy(); }
bool move_next() {
if (!handle.done()) {
handle.resume();
return !handle.done();
}
return false;
}
T current() { return handle.promise().current_value; }
};
Generator <int> range(int start, int end) {
for (int i = start; i < end; ++i) {
co_yield i; // 生成值
}
}
说明:
promise_type保存当前值、异常等。initial_suspend让协程立即开始。final_suspend让协程在完成后暂停,等待外部销毁。yield_value存储当前值,然后暂停。
3. 使用 Generator
int main() {
auto gen = range(1, 6); // 生成 1..5
while (gen.move_next()) {
std::cout << "Value: " << gen.current() << '\n';
}
}
输出:
Value: 1
Value: 2
Value: 3
Value: 4
Value: 5
4. 一个简易的 Task(异步函数)
#include <coroutine>
#include <future>
#include <chrono>
struct Task {
struct promise_type {
std::promise <void> promise;
Task get_return_object() {
return Task{ .handle = std::coroutine_handle <promise_type>::from_promise(*this) };
}
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept {
promise.set_value();
return {};
}
void unhandled_exception() { promise.set_exception(std::current_exception()); }
void return_void() {}
};
std::coroutine_handle <promise_type> handle;
std::future <void> get_future() { return handle.promise().promise.get_future(); }
};
Task async_print(int n, std::chrono::milliseconds delay) {
std::cout << "Start: " << n << '\n';
co_await std::suspend_always{}; // 这里可以放置真正的异步等待
std::this_thread::sleep_for(delay);
std::cout << "End: " << n << '\n';
}
说明:
- 这里的
async_print通过co_await暂停,模拟异步操作。实际项目中可替换为真正的异步等待器(如std::future、std::experimental::awaitable等)。
5. 结合 Generator 与 Task
下面的例子展示如何将异步任务与生成器组合,产生一个“异步流”。
Generator<std::future<int>> async_numbers(int count) {
for (int i = 0; i < count; ++i) {
std::promise <int> prom;
auto fut = prom.get_future();
std::thread([i, prom = std::move(prom)]() mutable {
std::this_thread::sleep_for(std::chrono::milliseconds(100 * i));
prom.set_value(i * i); // 返回平方值
}).detach();
co_yield fut; // 生成一个 future
}
}
消费:
int main() {
auto gen = async_numbers(5);
while (gen.move_next()) {
auto fut = gen.current();
std::cout << "Result: " << fut.get() << '\n';
}
}
输出(大约每 100ms 一行):
Result: 0
Result: 1
Result: 4
Result: 9
Result: 16
6. 小结
- 协程让我们能够以同步的写法描述异步逻辑,代码更简洁易读。
Generator与Task是两种常见的协程用例,分别对应生成器和异步任务。- 在实际项目中,建议使用标准库
std::experimental::generator(C++23)或第三方库(如cppcoro)来避免手写 Promise 细节。
掌握协程后,你可以实现更高效的异步 I/O、事件驱动模型以及基于协程的轻量级线程池。祝你编码愉快!