C++20 通过协程(coroutine)机制为异步编程带来了革命性的简化。传统的异步 I/O 需要回调、状态机或多线程,易导致“回调地狱”,而协程允许我们以同步代码的写法实现异步流程。下面以 asio(Boost.Asio 或独立的 ASIO)为例,演示如何利用协程实现一个简单的异步 TCP 客户端。
1. 环境准备
- 编译器支持 C++20(例如 GCC 11+,Clang 13+,MSVC 16.10+)
- 安装 ASIO(如果你不想依赖 Boost,直接使用独立版)
# 安装独立版 ASIO(假设你在 Linux)
sudo apt-get install libasio-dev
2. 基本思路
-
异步操作包装
`。
ASIO 提供了async_*形式的接口,如async_connect,async_read,async_write。这些函数接受一个可调用对象作为完成处理器(handler)。我们要把它们包装成返回std::future或更好,直接返回 `awaitable -
协程入口
使用asio::co_spawn创建一个协程。协程内部使用co_await等待异步操作完成,代码像同步流程一样直观。 -
错误处理
协程内部的co_await可以捕获异常,使用try-catch结构处理错误。
3. 示例代码
以下代码展示了一个异步 TCP 客户端,它连接到服务器、发送请求并接收响应:
#include <asio.hpp>
#include <asio/steady_timer.hpp>
#include <asio/coroutine.hpp>
#include <iostream>
#include <string>
using asio::ip::tcp;
using asio::awaitable;
using asio::use_awaitable;
using namespace std::chrono_literals;
// 简单的 awaitable wrapper
awaitable <void> async_echo(tcp::socket& socket, const std::string& message)
{
// 发送消息
std::size_t sent = co_await asio::async_write(
socket,
asio::buffer(message),
use_awaitable
);
// 接收回复
std::vector <char> buf(1024);
std::size_t recvd = co_await asio::async_read(
socket,
asio::buffer(buf),
use_awaitable
);
std::string reply(buf.data(), recvd);
std::cout << "Received: " << reply << '\n';
}
awaitable <void> client(const std::string& host, const std::string& port)
{
try
{
// 获取 I/O 上下文
asio::io_context io_ctx{1};
// 创建协程的 socket
tcp::socket socket{io_ctx};
// 解析地址
auto endpoints = co_await tcp::resolver{io_ctx}.async_resolve(host, port, use_awaitable);
// 连接
co_await asio::async_connect(socket, endpoints, use_awaitable);
std::cout << "Connected to " << host << ':' << port << '\n';
// 发送并接收
co_await async_echo(socket, "Hello from C++20 coroutine!");
// 关闭 socket
socket.close();
}
catch (const std::exception& ex)
{
std::cerr << "Error: " << ex.what() << '\n';
}
}
int main()
{
asio::io_context io_ctx;
// 创建协程并运行
asio::co_spawn(io_ctx, client("127.0.0.1", "12345"), asio::detached);
io_ctx.run();
return 0;
}
代码说明
use_awaitable:将 ASIO 的异步接口转换为协程可co_await的形式。- `awaitable `:协程返回类型,表示没有返回值;若有返回值,可改为 `awaitable`。
co_await:等待异步操作完成,内部会挂起当前协程,直到操作完成。asio::co_spawn:在指定的io_context上启动协程,asio::detached表示不关心协程返回值。
4. 性能与优势
- 简洁性:异步代码像同步一样书写,消除回调嵌套。
- 可组合:协程间可以使用
co_await进行组合,实现复杂的异步流程。 - 资源友好:协程在挂起时几乎不占用栈空间,轻量级。
- 错误传播:异常在协程内抛出,外层可统一捕获。
5. 进阶技巧
-
使用
steady_timer实现超时auto timer = co_await asio::steady_timer::async_wait(use_awaitable); -
多协程并发
通过asio::co_spawn生成多个协程,每个协程处理不同连接,io_context共享 I/O 资源。 -
自定义 awaitable
为复杂的异步操作编写自己的 awaitable 类型,进一步提高可读性。
6. 常见坑
- 使用错误的
use_awaitable:一定要在asio::async_*后面加use_awaitable,否则返回的是回调方式。 - 错误未捕获:协程内部若不捕获异常,异常会被
io_context捕获并打印,但程序会继续运行,可能导致资源泄露。 - 缺乏
io_context.run():所有协程必须在io_context.run()循环中执行,否则不会被调度。
7. 小结
C++20 协程让异步 I/O 代码既高效又易读。通过 asio 的 awaitable 接口,开发者可以在不牺牲性能的前提下,写出类似同步的异步程序。随着标准库持续完善,未来我们将看到更多针对网络、文件和数据库的原生协程支持,极大提升 C++ 在异步领域的竞争力。