取消时可以将boost :: asio :: yield_context用作deadline_timer处理程序吗?

时间:2014-08-02 02:48:22

标签: c++ boost boost-asio

我希望能够对特定事件进行异步等待。这里有很多类似的问题和答案(所有编译和工作对我来说),但没有我的具体情况。基本上,我需要做的是async_wait,将yield上下文作为处理程序传递给无限期等待的计时器,然后被另一个线程取消。

例如,有this question执行非常类似的操作,但它不使用yield上下文,而是使用单独的独立处理程序。还有类似this question的东西,它使用yield上下文,但等待指定的时间。

我可以将我的代码更改为上面两个示例中的任何一个,并且工作正常。但是出于某种原因,当我将yield_context处理程序和取消的计时器组合在一起时,我得到以下异常:

libc++abi.dylib: terminating with uncaught exception of type boost::exception_detail::clone_impl<boost::exception_detail::current_exception_std_exception_wrapper<std::runtime_error> >: 
Program ended with exit code: 9

从我可以看出,在尝试调用完成处理程序时(在这种情况下是yield上下文),事情看起来很糟糕。

好吧,有足够的唠叨,这里是代码。我试图用尽可能简单的方式来说明它:

班级:

class Foo {
public:
  Foo() : work_(io_service_), timer_(io_service_) {
    thread_pool_.create_thread(boost::bind(&boost::asio::io_service::run, &io_service_));
    timer_.expires_from_now(boost::posix_time::pos_infin);
  }
  ~Foo() {
    io_service_.stop();
    thread_pool_.join_all();
  }

  void Wait(const boost::asio::yield_context& context) {
    std::cout << "Waiting" << std::endl;
    timer_.async_wait(context);
    std::cout << "Done waiting" << std::endl;
  }

  void Notify() {
    std::cout << "Notifying" << std::endl;
    timer_.cancel();
  }

  void Write(int num) {
    std::cout << "Sending buffer event" << std::endl;
    Notify();
    std::cout << "Sent buffer event" << std::endl;
  }

  void Read(const boost::asio::yield_context& context) {
    std::cout << "Waiting on buffer event, size is " << buffer_.size() << std::endl;
    Wait(context);
    std::cout << "Received buffer event, size is now " << buffer_.size() << std::endl;
  }

  std::vector<int> buffer_;
  boost::asio::io_service io_service_;
  boost::thread_group thread_pool_;
  boost::asio::io_service::work work_;
  boost::asio::deadline_timer timer_;
};

主:

boost::shared_ptr<Foo> foo(new Foo());    
boost::asio::spawn(foo->io_service_, boost::bind(&Foo::Read, foo, _1));
boost::this_thread::sleep(boost::posix_time::seconds(2));
foo->Write(1);
boost::this_thread::sleep(boost::posix_time::seconds(4));

输出:

Waiting on buffer event
Waiting
Sending buffer event
Notifying
Sent buffer event
libc++abi.dylib: terminating with uncaught exception of type boost::exception_detail::clone_impl<boost::exception_detail::current_exception_std_exception_wrapper<std::runtime_error> >: 

现在,如果我将wait方法更改为在调用cancel之前超时的时间,一切都很好。即:

void Wait(const boost::asio::yield_context& context) {
    std::cout << "Waiting" << std::endl;
    timer_.expires_from_now(boost::posix_time::seconds(1));
    timer_.async_wait(context);
    std::cout << "Done waiting" << std::endl;
  }

或者,如果我改变等待使用单独的处理程序方法,一切都很好。即:

void Handler() {
  std::cout << "Handler!" << std::endl;
}

void Wait(const boost::asio::yield_context& context) {       
  std::cout << "Waiting" << std::endl;
  timer_.async_wait(boost::bind(&Foo::Handler, this));
  std::cout << "Done waiting" << std::endl;
}

我假设必须有一些更简单的东西我在这里失踪:要么由于某种原因这是不可能的,要么我犯了一些愚蠢的错误。无论如何,提前谢谢。

1 个答案:

答案 0 :(得分:1)

async_wait()操作为cancelled,导致异步操作失败,错误代码为boost::asio::error::operation_aborted。如Stackful Coroutines documentation中所述,当boost::asio::yield_context检测到异步操作失败时,它会将boost::system::error_code转换为system_error异常并抛出。在协程中,考虑:

  • 使用context[error_code]处理程序启动异步操作,导致yield_context在失败时填充提供的boost::system::error_code而不是抛出。

    boost::system::error_code error;
    timer_.async_wait(context[error]); // On failure, populate error.
    
  • 抓住system_error并予以取消。


失败Boost.Asio将填充boost::system::error_code如果应用程序能够接收它,否则它将抛出异常。这种模式可以在整个Boost.Asio中观察到:

  • 所有asynchronous operation处理程序都接受左值const boost::system::error_code作为其第一个参数。因此,启动函数不应抛出,因为应用程序将被告知处理程序内的错误。当使用放弃额外参数的函子时,例如boost::bind
  • ,这并不总是很明显
  • 重载同步操作以支持投掷和非投掷版本。例如,timer.cancel()将失败,timer.cancel(boost::system::error_code&)将设置error_code以指示错误。
  • 如上所述,当异步操作在堆栈协程中失败且yield_context处理程序未提供boost::system::error_code时,将抛出system_error异常。
  • 使用futures时,如果异步操作失败,则error_code将转换为system_error异常并通过future传回给调用方。

以下是基于原始问题的完整最小示例。

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>

int main()
{
  boost::asio::io_service io_service;
  boost::asio::deadline_timer timer(io_service);
  timer.expires_from_now(boost::posix_time::pos_infin);

  boost::asio::spawn(io_service,
    [&](boost::asio::yield_context yield)
    {
      // As only one thread is processing the io_service, the posted
      // timer cancel will only be invoked once the coroutine yields.
      io_service.post([&](){ timer.cancel(); });

      // Initiate an asynchronous operation, suspending the current coroutine,
      // and allowing the io_service to process other work (i.e. cancel the 
      // timer).  When the timer is cancelled, the asynchronous operation is
      // completed with an error,  causing the coroutine to resume.  As an
      // error_code is provided, the operation will not throw on failure.
      boost::system::error_code error;
      timer.async_wait(yield[error]);
      assert(error == boost::asio::error::operation_aborted);
    });

  io_service.run();
}