执行取消操作时,boost::asio::yield_text是否可以用作deadline_timer处理程序

Can a boost::asio::yield_context be used as a deadline_timer handler when doing cancel?

本文关键字:deadline 程序 处理 timer 是否 text boost 取消 asio 执行 yield      更新时间:2023-10-16

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

例如,有一个问题做了一些非常类似的事情,但它没有使用yield上下文,而是使用了一个单独的独立处理程序。还有类似这样的问题,它使用yield上下文,但等待指定的时间。

我可以更改我的代码,使其看起来像上面两个示例中的任何一个,并且一切都很好。但由于某些原因,当我将yield_text处理程序和取消的计时器组合在一起时,我会得到以下异常:

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_;
};

Main:

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;
  }

或者,如果我将wait更改为使用单独的处理程序方法,那么一切都很好。即:

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;
}

我想我一定错过了一些更简单的东西:要么因为某种原因这是不可能的,要么我犯了一些愚蠢的错误。不管怎样,提前谢谢。

async_wait()操作被取消,导致异步操作失败,错误代码为boost::asio::error::operation_aborted。如Stackful Coroutines文档中所述,当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:

  • 所有异步操作处理程序都接受一个左值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();
}