You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 03:45:40