我希望能够对特定事件进行异步等待。这里有很多类似的问题和答案(所有问题和答案都为我汇编和工作(,但没有一个与我的具体场景有关。基本上,我需要做的是一个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();
}