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

Boost.Asio:如何取消Awaitable且不引发程序终止?

Boost Asio协程co_spawn取消导致程序终止问题的解决办法

问题描述

示例代码

#include <boost/asio/awaitable.hpp>
#include <boost/asio/bind_cancellation_slot.hpp>
#include <boost/asio/cancellation_signal.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/io_context.hpp>
#include <boost/asio/use_awaitable.hpp>

namespace ba = boost::asio;

ba::cancellation_signal cancel_sub;
void subscribe(ba::io_context &context)
{
  ba::co_spawn(
    context,
    []() -> ba::awaitable<void> { co_return; },
    ba::bind_cancellation_slot(cancel_sub.slot(), ba::detached));
  cancel_sub.emit(ba::cancellation_type::all);
}

int main()
{
  ba::io_context ctx;
  subscribe(ctx);
  ctx.run();
  return 0;
}

错误输出

程序运行后因内部抛出未捕获异常终止:

terminate called after throwing an instance of 'boost::wrapexcept<boost::system::system_error>'
  what():  co_await: Operation canceled [system:125]

尝试过的无效方案

  • 在协程内禁用取消抛出异常:
    co_await ba::this_coro::throw_if_cancelled(false);
    
  • 在协程开头重置取消状态:
    co_await ba::this_coro::reset_cancellation_state(ba::enable_total_cancellation());
    

异常流程分析

通过Boost内部代码追踪,异常在co_spawn_entry_point中两次触发:

template <typename Handler, typename Executor, typename Function>
awaitable<awaitable_thread_entry_point, Executor> co_spawn_entry_point(
    awaitable<void, Executor>*, co_spawn_state<Handler, Executor, Function> s)
{
  (void) co_await co_spawn_dispatch{};

  (co_await awaitable_thread_has_context_switched{}) = false;
  std::exception_ptr e = nullptr;
  try
  {
    // 第一次:调用用户协程时检测到取消并抛出,被catch捕获
    co_await s.function();
  }
  catch (...)
  {
    e = std::current_exception();
  }

  bool switched = (co_await awaitable_thread_has_context_switched{});
  if (!switched)
    (void) co_await co_spawn_post(); // 第二次:await_transform中再次检查取消,抛出异常未被捕获

  (dispatch)(s.handler_work.get_executor(),
      [handler = std::move(s.handler), e]() mutable
      {
        std::move(handler)(e);
      });
}

解决方案

方案1:延迟触发取消信号

问题根源是协程尚未完成初始化就被取消,导致内部co_spawn_post阶段检测到取消状态并抛出未捕获异常。通过post延迟取消信号的触发,让用户协程先执行完毕:

void subscribe(ba::io_context &context)
{
  ba::co_spawn(
    context,
    []() -> ba::awaitable<void> { co_return; },
    ba::bind_cancellation_slot(cancel_sub.slot(), ba::detached));
  // 用post将取消操作放入io_context队列,等待协程执行完成后再触发
  ba::post(context, [&](){ cancel_sub.emit(ba::cancellation_type::all); });
}

方案2:自定义Handler捕获异常

替代默认的detached handler,自定义一个能捕获所有异常的handler,避免未处理异常终止程序:

// 自定义handler,捕获并处理异常
struct safe_detached
{
  void operator()(std::exception_ptr e) const noexcept
  {
    if (e)
    {
      try
      {
        std::rethrow_exception(e);
      }
      catch (const std::exception& ex)
      {
        // 可根据需求处理异常,比如打印日志或直接忽略
        // std::cerr << "Caught exception: " << ex.what() << std::endl;
      }
    }
  }
};

void subscribe(ba::io_context &context)
{
  ba::co_spawn(
    context,
    []() -> ba::awaitable<void> { co_return; },
    ba::bind_cancellation_slot(cancel_sub.slot(), safe_detached{}));
  cancel_sub.emit(ba::cancellation_type::all);
}

方案3:调整取消类型(可选)

如果业务允许,可将取消类型从all改为terminal或其他更严格的类型,减少内部触发异常的场景,但此方案不保证适用于所有情况:

cancel_sub.emit(ba::cancellation_type::terminal);

内容的提问来源于stack exchange,提问作者Jean-Michaël Celerier

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 05:43:28