C++20环境下co_await组合async_read与async_wait触发未捕获异常问题
C++20中co_await组合async_read与async_wait引发未捕获异常修复方案
问题现象
- 程序触发无法捕获的异常
- 出现
boost::asio::detail::cancellation_handler_base::call[virtual] == 0xFFFFFFFFFFFFFFFF错误,对应错误代码片段:
void emit(cancellation_type_t type) { if (handler_) handler_->call(type); }
问题根源
使用||操作符组合async_read和async_wait时,其中一个操作完成后Asio会自动取消另一个未完成的操作。原代码存在两个关键问题:
- 服务端循环中复用同一个定时器对象,重复调用
expires_after导致旧的等待操作被取消时,内部handler管理出现非法状态,触发未定义行为。 - 异步操作的错误处理不统一,部分操作使用
use_awaitable抛出异常,部分使用use_nothrow_awaitable返回错误,导致取消逻辑中的异常无法被正确捕获。
修复方案
- 循环内创建新定时器对象:避免复用定时器导致的旧操作取消时的handler问题。
- 统一使用
use_nothrow_awaitable:所有异步操作都通过返回值处理错误,避免抛出未捕获异常。 - 完善错误处理逻辑:操作失败时及时退出循环,防止无效的后续操作。
修复后的服务端代码
#include <iostream> #include <boost/asio.hpp> #include <boost/asio/thread_pool.hpp> #include <boost/asio/experimental/awaitable_operators.hpp> #include <boost/asio/experimental/as_tuple.hpp> #include <boost/asio/co_spawn.hpp> #include <boost/asio/detached.hpp> #include <boost/asio/io_context.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/signal_set.hpp> #include <cstdio> using boost::asio::ip::tcp; using boost::asio::awaitable; using boost::asio::co_spawn; using boost::asio::detached; using boost::asio::steady_timer; using boost::asio::use_awaitable; using boost::asio::thread_pool; using std::chrono::steady_clock; using boost::asio::buffer; namespace this_coro = boost::asio::this_coro; constexpr auto use_nothrow_awaitable = boost::asio::experimental::as_tuple(boost::asio::use_awaitable); using namespace std::literals::chrono_literals; using namespace boost::asio::experimental::awaitable_operators; awaitable<void> echo(tcp::socket socket) { try { char data[2000]; for (;;) { steady_timer timer(co_await this_coro::executor); timer.expires_after(2ms); auto result = co_await( async_read(socket, buffer(data, 2000), use_nothrow_awaitable) || timer.async_wait(use_nothrow_awaitable) ); if (result.index() == 1) { // 超时,无处理 } else { auto [e, n] = std::get<0>(result); if (!e) { if (n > 0) { auto [write_e, write_n] = co_await async_write( socket, buffer(data, n), use_nothrow_awaitable ); if (write_e) { std::cout << "Write error: " << write_e.message() << std::endl; break; } } } else { std::cout << "Read error: " << e.message() << std::endl; break; } } } } catch (const std::exception& e) { std::printf("echo Exception: %s\n", e.what()); } } awaitable<void> listener() { auto executor = co_await this_coro::executor; tcp::acceptor acceptor(executor, {tcp::v4(), 5555}); for (;;) { tcp::socket socket = co_await acceptor.async_accept(use_awaitable); co_spawn(executor, echo(std::move(socket)), detached); } } int main(int argc, char* argv[]) { try { thread_pool pol(8); boost::asio::signal_set signals(pol, SIGINT, SIGTERM); signals.async_wait([&](auto, auto) { pol.stop(); }); co_spawn(pol, listener(), detached); pol.wait(); } catch (const std::exception& e) { std::printf("Exception: %s\n", e.what()); } }
修复后的客户端代码
#include <boost/asio.hpp> #include <boost/asio/thread_pool.hpp> #include <boost/asio/experimental/awaitable_operators.hpp> #include <boost/asio/experimental/as_tuple.hpp> #include <boost/asio/co_spawn.hpp> #include <boost/asio/detached.hpp> #include <boost/asio/io_context.hpp> #include <boost/asio/ip/tcp.hpp> #include <boost/asio/signal_set.hpp> #include <cstdio> using boost::asio::ip::tcp; using boost::asio::awaitable; using boost::asio::co_spawn; using boost::asio::detached; using boost::asio::steady_timer; using boost::asio::use_awaitable; using std::chrono::steady_clock; using boost::asio::thread_pool; using boost::asio::buffer; namespace this_coro = boost::asio::this_coro; constexpr auto use_nothrow_awaitable = boost::asio::experimental::as_tuple(boost::asio::use_awaitable); using namespace std::literals::chrono_literals; thread_pool io_context(3); awaitable<void> timeout(steady_clock::duration duration) { steady_timer timer(co_await this_coro::executor); timer.expires_after(duration); co_await timer.async_wait(use_nothrow_awaitable); } awaitable<void> echo3(tcp::socket socket) { try { char data[4024]; for (;;) { co_await timeout(2ms); auto [e, n] = co_await async_write( socket, buffer(data, 2000), use_nothrow_awaitable ); if (e) { std::printf("Write error: %s\n", e.message().c_str()); break; } } } catch (const std::exception& e) { std::printf("echo3 Exception: %s\n", e.what()); } } awaitable<void> listener() { try { auto executor = co_await this_coro::executor; tcp::socket socket(executor); tcp::resolver resolver(executor); auto endpoints = resolver.resolve("127.0.0.1", "5555"); auto [connect_e] = co_await async_connect(socket, endpoints, use_nothrow_awaitable); if (connect_e) { std::printf("Connect error: %s\n", connect_e.message().c_str()); co_return; } co_spawn(executor, echo3(std::move(socket)), detached); } catch (const std::exception& e) { std::printf("listener Exception: %s\n", e.what()); } } int main() { try { boost::asio::signal_set signals(io_context, SIGINT, SIGTERM); signals.async_wait([&](auto, auto) { io_context.stop(); }); for (size_t i = 0; i < 3; i++) { co_spawn(io_context, listener(), detached); } io_context.wait(); } catch (const std::exception& e) { std::printf("main Exception: %s\n", e.what()); } }
内容的提问来源于stack exchange,提问作者Mina Dethahal
相关产品推荐
相关产品推荐

