协程不能自动简化异步编程,它仅提供挂起/恢复机制,不内置调度、线程管理或运行时支持,需手动处理生命周期、避免栈变量跨挂起、显式捕获异常并依赖第三方库实现awaitable语义。
协程不能自动简化异步编程,它只是把回调地狱换成了「看似同步、实则挂起」的控制流——前提是正确管理生命周期、避免栈溢出、不滥用 co_await 在非可挂起点。
co_await 不等于「自动异步转同步」协程本身不执行调度,也不绑定线程或事件循环。你写 co_await async_read(socket, buf),背后仍需一个实现了 awaitable 接口的对象(比如返回 task 的函数),而该对象的 await_suspend 才真正注册回调到 I/O 多路复用器(如 epoll 或 IOCP)。
常见误判是以为只要用了 co_await,就能像 Python 的 async/await 那样开箱即用。C++20 没有标准运行时,所有调度逻辑必须自己搭或依赖第三方库(如 libunifex、cppcoro、Boost.ASIO 1.78+)。
co_await 只触发挂起/恢复协议,不启动任何线程、不管理线程池asyncio.run() 对应物;你得手动调用 task.start() 或 executor.run()
co_return 或在销毁前未完成协程,会导致未定义行为(UB),而非抛异常传统回调的问题是嵌套深、错误传播难、状态分散;协程表面扁平,但引入了新的崩溃路径:
taskhandle_request(tcp_socket& sock) { auto buf = std::make_unique (1024); // ✅ 正确:buf 生命周期覆盖整个协程 ssize_t n = co_await sock.async_read(buf.get(), 1024); co_await sock.async_write("HTTP/1.1 200 OK\r\n", 18); }
下面这段就危险:
taskbad _example(tcp_socket& sock) { char local_buf[1024]; // ❌ 栈变量,协程挂起后可能已被销毁 co_await sock.async_read(local_buf, 1024); // UB 高发区 }
try { co_await op(); } catch(...) { ... },不会自动传播到调用方协程co_await 行的单步支持有限,常跳过挂起点直接到恢复点当异步操作有明确顺序依赖、且中间状态需多次复用时,协程优势明显。例如实现一个带重试、超时、进度通知的文件下载:
taskdownload_with_retry(http_client& client, string_view url) { for (int i = 0; i < 3; ++i) { auto res = co_await client.get(url); // 等待完整响应 if (res.status == 200) { co_await write_to_disk(res.body); co_return true; } co_await timer::sleep(1s); // 挂起而不阻塞线程 } co_return false; }
on_success → on_write_complete → on_timeout),状态变量(重试次数、临时 buffer)自然保留在作用域内co_await with_timeout(op, 5s) 封装,无需为每个回调单独设 timer ID 并清理timer::sleep 必须是真正的 awaitable(内部调用 epoll_wait 或 WaitForSingleObject),不是 std::this_thread::sleep_for
协程最大的陷阱不是语法,而是误以为它解决了资源生命周期问题——它恰恰让生命周期更难追踪。栈变量、裸指针、未 move 的 unique_ptr,在挂起点前后都可能失效。写协程代码时,眼睛要盯着「这个变量在下次 co_await 返回时还活着吗?」,而不是只看缩进是否整齐。