在 C++20 里,协程(coroutine)被正式纳入标准,提供了 co_await、co_yield 和 co_return 等关键字,使得异步编程可以像同步代码一样书写。下面我们用 C++20 协程来实现一个简易的异步 I/O 框架,演示如何利用 std::future、std::promise 与协程共同完成网络请求或文件读取等任务。
1. 先决条件
- 编译器:支持 C++20 并已开启协程支持,例如
g++-13 -std=c++20 -fcoroutines 或 clang++-16 -std=c++20 -fcoroutines-ts。
- 依赖库:本文仅使用标准库,无需额外依赖。
2. 协程与未来的基本关系
协程的执行体可以暂停(co_await)并在事件完成后继续。我们将定义一个 `Task
` 类型,它内部封装了 `std::future`,并通过 `co_await` 与事件源交互。
“`cpp
#include
#include
#include
#include
#include
#include
template
struct Task {
struct promise_type {
std::promise
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(T value) noexcept { prom.set_value(value); }
void unhandled_exception() noexcept { prom.set_exception(std::current_exception()); }
};
std::future
fut;
Task(std::future
f) : fut(std::move(f)) {}
Task(const Task&) = delete;
Task(Task&&) = default;
};
“`
> 这里 `promise_type` 的 `initial_suspend` 与 `final_suspend` 均为 `suspend_never`,意味着协程立即开始执行并在完成时结束。
—
## 3. 模拟异步 I/O
为了演示,假设我们有一个异步读取文件的接口 `async_read_file`,它返回 `Task<std::vector>`。真正的异步 I/O 需要系统调用或第三方库,但我们使用 `std::thread` 模拟延迟。
“`cpp
Task<std::vector> async_read_file(const std::string& path) {
std::promise<std::vector> prom;
auto fut = prom.get_future();
std::thread([path, prom = std::move(prom)]() mutable {
// 模拟 I/O 延迟
std::this_thread::sleep_for(std::chrono::milliseconds(100));
// 读取内容(这里仅返回 dummy 数据)
std::vector
data = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘C++’, ‘!’};
prom.set_value(std::move(data));
}).detach();
// 让协程等待 future 完成
co_await std::suspend_always{};
// 当事件触发后,future 已 ready,可以获取结果
co_return fut.get();
}
“`
> `co_await std::suspend_always{}` 用于让协程暂停,等到 `fut` 可获取结果后才继续。实际项目中可用 `co_await` 结合自定义 awaiter。
—
## 4. 组合协程实现更复杂逻辑
下面演示如何将多个异步读取串联起来,形成流水线:
“`cpp
Task
process_files(const std::vector& paths) {
int total = 0;
for (const auto& p : paths) {
auto data = co_await async_read_file(p); // 等待文件读取完成
total += static_cast
(data.size()); // 简单统计字节数
}
co_return total; // 返回所有文件的总字节数
}
“`
在主函数中启动协程并等待结果:
“`cpp
int main() {
std::vector files = {“a.txt”, “b.txt”, “c.txt”};
auto task = process_files(files);
std::cout << "Total bytes: " << task.fut.get() << std::endl;
}
“`
—
## 5. 进一步改进:使用 Awaiter
上面示例中使用了 `co_await std::suspend_always{}`,这不够灵活。下面给出一个 `FutureAwaiter`,可以直接 `co_await` 一个 `std::future`。
“`cpp
template
struct FutureAwaiter {
std::future
& 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) {
std::thread([this, h](){ fut.wait(); h.resume(); }).detach();
}
R await_resume() { return fut.get(); }
};
template
FutureAwaiter
operator co_await(std::future& f) {
return FutureAwaiter
{f};
}
“`
现在 `async_read_file` 可以简化为:
“`cpp
Task<std::vector> async_read_file(const std::string& path) {
std::promise<std::vector> prom;
auto fut = prom.get_future();
std::thread([path, prom = std::move(prom)]() mutable {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::vector
data = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘ ‘, ‘C++’, ‘!’};
prom.set_value(std::move(data));
}).detach();
co_return co_await fut; // 直接等待 future 完成
}
“`
—
## 6. 小结
– C++20 协程提供了极简的语法,让异步逻辑像同步代码一样可读。
– 通过自定义 `Task
` 和 `FutureAwaiter`,我们可以轻松把 `std::future` 与协程整合。
– 本示例使用 `std::thread` 模拟异步 I/O,实际项目中可替换为网络、文件或数据库的真正异步调用。
从此,你可以在 C++20 项目中更自如地使用协程,写出既优雅又高性能的异步代码。祝编码愉快!</std::vector</std::vector</std::vector</std::vector</std::vector