c++ - boost::asio::yield_context:意外的 forced_unwind 异常

标签 c++ boost boost-asio coroutine

我正在尝试为 boost::asio 编写自定义异步函数,如 here 所述.

但是我在 result.get

上遇到了 boost::coroutines::detail::forced_unwind 异常
#include <boost/chrono.hpp>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>

#include <iostream>

namespace asio = ::boost::asio;


template <typename Timer, typename Token>
auto my_timer (Timer& timer, Token&& token)
{
  typename asio::handler_type<Token,
      void (::boost::system::error_code const)>::type
      handler (std::forward<Token> (token));

  asio::async_result<decltype (handler)> result (handler);

  timer.async_wait (handler);
  return result.get (); // Got forced_unwind exception here.
}

int main ()
{
  asio::io_service io;
  asio::steady_timer timer (io, ::boost::chrono::seconds (1));

  asio::spawn (io, [&] (asio::yield_context yield)
      {
      try {
        std::cout << "my_timer enter\n";
        my_timer (timer, yield);
        std::cout << "my_timer returns\n";
      }
      catch (const boost::coroutines::detail::forced_unwind& e)
      { 
        std::cout << "boost::coroutines::detail::forced_unwind\n"; 
      }
    }
  );

  io.run ();
}

Coliru 上的相同代码

更新:

行为存在于:

Darwin 14.0.0 (MacOS 10.10) 
clang version 3.6.0 (trunk 216817) and gcc version 4.9.1 (MacPorts gcc49 4.9.1_1) 
boost 1.57

Red Hat 6.5
gcc version 4.7.2 20121015 (Red Hat 4.7.2-5) (GCC)
boost 1.57 and 1.56
(the example code was trivially modified because gcc 4.7 does not support c++14 mode)

最佳答案

简而言之,您需要创建处理程序的拷贝,例如将其发布到 io_service 中,然后再尝试获取 async_result 以保持协程活着。


Boost.Asio 通过销毁协程来防止不可恢复的协程无限期挂起,从而导致协程的堆栈展开。协程对象将在其销毁期间抛出 boost::coroutines::detail::forced_unwind,导致挂起的堆栈展开。 Asio 通过以下方式实现这一点:

  • yield_context CompletionToken 维护协程的 weak_ptr
  • 当专门handler_type::type handler构造完成后,通过CompletionToken的weak_ptr获取协程的shared_ptr。当处理程序作为完成处理程序传递给异步操作时,处理程序及其 shared_ptr 被复制。调用处理程序时,它会恢复协程。
  • 调用时 async_result::get() , 特化将重置在构造期间传递给 async_result 的处理程序拥有的协程 shared_ptr,然后产生协程。

这里试图说明代码的执行。 |中的路径表示事件栈,:表示挂起栈,箭头表示控制权转移:

boost::asio::io_service io_service;
boost::asio::spawn(io_service, &my_timer);
`-- dispatch a coroutine creator
    into the io_service.
io_service.run();
|-- invoke the coroutine entry
|   handler.
|   |-- create coroutine
|   |   (count: 1)
|   |-- start coroutine        ----> my_timer()
:   :                                |-- create handler1 (count: 2)
:   :                                |-- create asnyc_result1(handler1)
:   :                                |-- timer.async_wait(handler)
:   :                                |   |-- create handler2 (count: 3)
:   :                                |   |-- create async_result2(handler2)
:   :                                |   |-- create operation and copy
:   :                                |   |   handler3 (count: 4)
:   :                                |   `-- async_result2.get()
:   :                                |       |-- handler2.reset() (count: 3)
|   `-- return                 <---- |       `-- yield
|       `-- ~entry handler           :
|           (count: 2)               :
|-- io_service has work (the         :
|   async_wait operation)            :
|   ...async wait completes...       :
|-- invoke handler3                  :
|   |-- resume                 ----> |-- async_result1.get()
:   :                                |   |--  handler1.reset() (count: 1)
|   `-- return                 <---- |   `-- yield
|       `-- ~handler3                :       :
|           |  (count: 0)            :       :
|           `-- ~coroutine()   ----> |       `-- throw forced_unwind

要解决这个问题,需要通过asio_handler_invoke()复制和调用handler何时恢复协程。例如,以下代码会将完成处理程序1 发布到调用处理程序 拷贝的io_service:

timer.async_wait (handler);

timer.get_io_service().post(
  std::bind([](decltype(handler) handler)
  {
    boost::system::error_code error;
    // Handler must be invoked through asio_handler_invoke hooks
    // to properly synchronize with the coroutine's execution
    // context.
    using boost::asio::asio_handler_invoke;
    asio_handler_invoke(std::bind(handler, error), &handler);
  }, handler)
);
return result.get ();

如图所示here ,使用这个附加代码,输出变为:

my_timer enter
my_timer returns

<子>1。完成处理程序代码可能会被清理一下,但正如我在回答 how to resume a Boost.Asio stackful coroutine from a different thread ,我观察到一些编译器选择了错误的 asio_handler_invoke 钩子(Hook)。

关于c++ - boost::asio::yield_context:意外的 forced_unwind 异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26999491/

相关文章:

c++ - 在 boost 中,如何将 boost 迭代器传递给以某种方式转换为 std::string 的函数

c++ - 链如何保证boost.asio中未决事件的正确执行

c++ - 使用 boost.process 同时读取和写入 child 的 stdio

c++ - 带有 cpp-netlib 的 Https 服务器

c++ - 这是对具有 C++ 继承层次结构的 namespace 的合理使用吗?

c++ - 模板模板参数的参数似乎是非模板类型

c++ - boost 间隔与多精度

C++ 初始化静态堆栈

c++ - 在 Symbian S60 上读取 SIM 联系人

c++ - 是否有一个 vector 可以处理非标准位长的整数?