协程函数必须返回task等协程类型,否则编译报错;需自定义task及awaitable(如async_read_awaiter);每个连接须独占协程栈资源;io_context必须先运行再启动协程。

协程函数必须返回 task 或类似可等待类型
直接写 co_await 却不指定返回类型,编译器会报错:‘co_await’ cannot be used in a function that does not return a coroutine type。C++20 本身不提供标准协程模板,得自己定义或用第三方库(如 cppcoro、libunifex),但最轻量做法是手写一个最小 task:
struct task {
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
注意:这个 task 不带结果、不可 co_await 等待,仅用于启动协程;若需返回值(比如读到的字节数),得扩展 promise_type 并加 value() 成员,否则后续链式 co_await 会失败。
co_await 等待的是 awaitable,不是裸 socket
不能写 co_await sock.read_some(...) —— basic_stream_socket::read_some 是同步阻塞函数,返回 size_t,不是 awaitable。必须包装成能挂起/恢复的 awaitable 对象。常见做法是用 asio::async_read + 自定义 awaiter:
struct async_read_awaiter {
asio::streambuf& buf;
asio::ip::tcp::socket& sock;
std::size_t bytes = 0;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle h) {
asio::async_read(sock, buf, [h](const asio::error_code& ec, std::size_t n) mutable {
if (ec) {
// 错误处理逻辑(如关闭连接)
} else {
static_cast<async_read_awaiter>(h.address())->bytes = n;
}
h.resume();
});
}
std::size_t await_resume() { return bytes; }
};</async_read_awaiter>
然后在协程里用:std::size_t n = co_await async_read_awaiter{buf, sock};。漏掉 await_suspend 中的 h.resume(),协程就永远挂起;忘记检查 ec,连接断开时程序可能继续读垃圾数据。
每个客户端连接必须绑定独立协程,不能复用栈变量
典型错误是把 asio::streambuf、临时 std::array 或 lambda 捕获的局部变量放在协程外声明,导致多个连接协程共享同一块内存,数据错乱。正确做法是:在 accept 后立即启动新协程,并把所有状态(缓冲区、socket)移入协程栈:
- 用
std::make_shared管理asio::streambuf,避免拷贝 - socket 用
std::move传入协程参数,防止被意外关闭 - 不要在协程里捕获外部
sock引用,而应接收右值引用:task handle_connection(asio::ip::tcp::socket&& sock)
否则高并发下会出现 “connection reset by peer” 或读到前一个连接的残留数据。
asio::io_context.run() 必须在协程启动前已运行
如果先调 co_await 再调 ctx.run(),协程会永远等不到 resume —— 因为没有线程在跑 event loop。典型部署结构是:
asio::io_context ctx{1}; // 单线程足够
server s{ctx};
std::thread t{[&] { ctx.run(); }}; // 必须先启线程
s.start(); // 此时才开始 accept + spawn coroutines
还容易忽略的是:协程中调用的 asio::post 或定时器也依赖 ctx 运行;若 ctx.stopped() 为 true,所有异步操作直接完成并返回 operation_aborted,需检查错误码而非假设成功。
协程的 suspend/resume 路径比 callback 更隐蔽,出问题时堆栈不显示 await 点,建议在关键 await 前后加日志,且始终验证 error_code —— 很多“没响应”其实只是连接被对端静默关闭了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











