在现代 C++ 开发中,异步编程已成为提高应用吞吐量和响应性的关键手段。C++20 引入了协程(coroutine)这一强大的语言特性,使得编写异步代码既直观又安全。本文将带你从基础概念到完整实现,系统阐述如何在 C++20 环境下构建高效、可维护的异步链式调用。
1. 协程的核心概念
| 关键字 | 说明 |
|---|---|
co_await |
暂停协程并等待某个可等待对象完成 |
co_return |
结束协程并返回结果 |
std::future/std::promise |
传统异步结果容器 |
std::suspend_always / std::suspend_never |
控制协程挂起/继续 |
协程并不是线程,而是轻量级的“挂起/恢复”单元。协程内部的执行状态会被编译器拆分成若干步骤,状态机由 promise_type 维护。
2. 简单的异步任务包装
#include <coroutine>
#include <iostream>
#include <chrono>
#include <thread>
#include <exception>
#include <optional>
struct task {
struct promise_type {
std::optional <int> result;
std::exception_ptr eptr;
task get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { eptr = std::current_exception(); }
void return_value(int v) { result = v; }
};
};
task async_add(int a, int b) {
std::cout << "开始异步加法\n";
co_await std::suspend_always{}; // 模拟异步延迟
co_return a + b;
}
此例展示了如何构造一个最小的 task,支持 co_await。实际项目中,你会使用更成熟的库(如 cppcoro 或 boost::asio)来替代手写的协程框架。
3. 链式调用:组合多个异步步骤
#include <future>
#include <vector>
#include <string>
struct async_chain {
std::vector<std::string> logs;
// 第一步:读取文件
std::future<std::string> read_file(std::string path) {
return std::async(std::launch::async, [=]() {
logs.push_back("读取文件:" + path);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return std::string("文件内容");
});
}
// 第二步:处理数据
std::future <int> process_data(std::string data) {
return std::async(std::launch::async, [=]() {
logs.push_back("处理数据:" + data);
std::this_thread::sleep_for(std::chrono::milliseconds(150));
return static_cast <int>(data.size());
});
}
// 第三步:写入数据库
std::future <void> write_db(int size) {
return std::async(std::launch::async, [=]() {
logs.push_back("写入数据库,大小:" + std::to_string(size));
std::this_thread::sleep_for(std::chrono::milliseconds(120));
});
}
// 链式执行
std::future <void> execute(std::string path) {
return std::async(std::launch::async, [=, this]() {
auto data = read_file(path).get();
auto sz = process_data(data).get();
write_db(sz).get();
});
}
};
关键点
- 错误传播:在链式调用中,异常会被
std::future捕获并通过get()抛出。可以在最终execute中加上try/catch统一处理。 - 并发度:使用
std::async时要注意线程池大小,避免线程泄露。
4. 与协程结合的异步链式调用
C++20 允许在协程内部 co_await std::future,从而实现更直观的链式代码。
#include <future>
#include <iostream>
task chain_example() {
auto fut1 = async_read_file("data.txt");
std::string content = co_await fut1; // 这里会挂起直到文件读取完成
auto fut2 = async_process(content);
int length = co_await fut2;
auto fut3 = async_write_db(length);
co_await fut3; // 同理,等待写入完成
std::cout << "链式异步完成\n";
}
上述代码几乎等价于传统 future 链,但语义更清晰。协程的优势在于 无需手动 .get(),异常会自然抛到调用方。
5. 性能与可维护性
| 方案 | 优点 | 缺点 |
|---|---|---|
std::async + std::future |
标准、易于迁移 | 线程池管理不方便,调度细粒度低 |
协程 + std::future |
代码直观,错误自动传播 | 需要自定义 promise_type 或使用第三方库 |
cppcoro / boost::asio |
丰富的工具箱,成熟生态 | 学习曲线较陡 |
- 线程复用:可使用
std::thread+std::promise组合实现自己的线程池,避免std::async的高开销。 - 错误处理:建议统一使用
try/catch处理链式调用中的异常,并记录堆栈信息。 - 日志:在协程中加入
co_await时的日志,可使用std::format或第三方日志库。
6. 小结
- C++20 协程让异步链式调用更接近同步代码的可读性。
std::future依旧是异步结果的标准容器,协程可以与之无缝协作。- 在实际项目中,建议使用成熟的协程框架(如
cppcoro、asio),并结合自定义线程池优化性能。 - 通过细粒度的日志和异常传播,可大幅提升异步代码的可维护性与可调试性。
提示:在高并发场景下,务必评估线程池大小与任务负载,避免因过度并发导致的上下文切换成本失控。
祝你在 C++20 的协程世界里写出更简洁、更高效的异步代码!