我希望能够对特定事件进行异步等待。这里有很多类似的问题和答案(所有编译和工作对我来说),但没有我的具体情况。基本上,我需要做的是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;
}
我假设必须有一些更简单的东西我在这里失踪:要么由于某种原因这是不可能的,要么我犯了一些愚蠢的错误。无论如何,提前谢谢。
答案 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中观察到:
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
异常。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();
}