Boost.Asio可等待操作符为何会吞噬异常?
问题原因
你遇到的问题源于Boost.Asio实验性awaitable_operators::||的语义特性:该操作符会等待第一个成功完成的可等待对象(awaitable),并取消另一个未完成的可等待对象。只有当两个可等待对象都失败时,才会传播最后一个失败的结果。
在你的代码中:
Throws函数会在100ms后抛出异常(属于失败完成)Cancelled函数使用了redirect_error,将定时器取消的错误码(operation_aborted)重定向到ec变量,而非抛出异常,因此即使定时器被取消,Cancelled仍会正常完成(属于成功完成)
当Throws抛出异常时,||操作符会立即取消Cancelled中的定时器,但Cancelled会因为redirect_error正常完成。此时||操作符认为已经有一个成功完成的可等待对象,因此会忽略Throws的异常,导致你的catch块无法捕获到预期的错误信息。
解决方案
要实现「任意一个可等待对象完成(无论成功或失败),都传播对应的结果/异常」的需求,你可以使用boost::asio::experimental::make_parallel_group来替代||操作符,它能更灵活地处理并行任务的结果。
修改后的完整代码
#include <boost/asio.hpp> #include <boost/asio/experimental/awaitable_operators.hpp> #include <boost/asio/experimental/parallel_group.hpp> #include <iostream> namespace io = boost::asio; using namespace boost::asio::experimental::awaitable_operators; using namespace std::chrono_literals; io::awaitable<void> Cancelled(io::steady_timer& cancelTimer) { boost::system::error_code ec; co_await cancelTimer.async_wait(io::redirect_error(io::use_awaitable, ec)); } io::awaitable<void> Throws(io::io_context& context) { io::steady_timer timer{ context, 100ms }; co_await timer.async_wait(io::use_awaitable); throw std::runtime_error("for testing"); } io::awaitable<void> Run(io::io_context& context, io::steady_timer& cancelTimer) { // 创建并行组,包含两个可等待任务 auto group = io::experimental::make_parallel_group( Throws(context), Cancelled(cancelTimer) ); // 等待任意一个任务完成,获取完成顺序和结果 auto [completion_order, results] = co_await group.async_wait( io::experimental::wait_for_one(), io::use_awaitable ); // 根据完成顺序处理结果:如果是Throws先完成,重抛异常 if (completion_order == 0) { std::get<0>(results).rethrow_exception(); } // 如果是Cancelled先完成,直接返回(无异常) } void RethrowException(std::exception_ptr e) { if (e) { std::rethrow_exception(e); } } int main() { try { io::io_context context; io::steady_timer cancelTimer{ context, 200ms }; io::co_spawn(context, Run(context, cancelTimer), RethrowException); context.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; } }
另一种简化方案(让Cancelled在取消时抛出异常)
如果你希望继续使用||操作符,可以修改Cancelled函数,让它在定时器被取消时抛出异常,这样||操作符会在两个任务都失败时传播异常:
io::awaitable<void> Cancelled(io::steady_timer& cancelTimer) { boost::system::error_code ec; co_await cancelTimer.async_wait(io::redirect_error(io::use_awaitable, ec)); // 当定时器被取消时,主动抛出异常 if (ec == io::error::operation_aborted) throw boost::system::system_error(ec); }
此时,当Throws先抛出异常,||操作符会取消Cancelled的定时器,Cancelled抛出异常,最终||表达式会传播第二个失败的结果(即Cancelled的异常)。如果你需要传播Throws的异常,还是推荐使用parallel_group方案。
内容的提问来源于stack exchange,提问作者Bernd36
相关产品推荐
相关产品推荐

