问题
I'm playing with the asio using coroutine, and would like to test how an async function can be called. I have the following code:
void async_foo(boost::asio::io_service& io_service, boost::asio::yield_context& yield)
{
using handler_type = boost::asio::handler_type<decltype(yield), void()>::type;
handler_type handler(std::forward<handler_type>(yield));
boost::asio::async_result<decltype(handler)> result(handler);
auto timer(std::make_shared<boost::asio::deadline_timer>(io_service, boost::posix_time::seconds(1)));
// the program crashes if I use &handler here
timer->async_wait([&handler](const boost::system::error_code) {
std::cout << "enter" << std::endl;
handler();
std::cout << "done" << std::endl;
});
result.get();
std::cout << "function finished" << std::endl;
return;
}
int main()
{
boost::asio::io_service io_service;
boost::asio::spawn(io_service, [&io_service](boost::asio::yield_context yield) {
std::cout << "hello" << std::endl;
async_foo(io_service, yield);
std::cout << "world" << std::endl;
});
io_service.run();
return 0;
}
It's weird that if I put &handler in the capture list the execution flow will be messed up and then it hits segmentation fault. But it runs without any problem if I use "handler" instead (then I need a copy in the lambda of course).
Searched around and couldn't find anything related. Thanks in advance for any help.
回答1:
As Tanner very nicely explains here:
- While spawn() adds work to the
io_service
(a handler that will start and jump to the coroutine), the coroutine itself is not work. To prevent theio_service
event loop from ending while a coroutine is outstanding, it may be necessary to add work to theio_service
before yielding.
If you add an extra trace line after the run()
:
io_service.run();
std::cout << "BYE" << std::endl;
Then, run a few times until you're lucky enough to not get the SEGV early, you can see output like the following:
hello
enter
done
BYE
Indeed, the io_service returns, destructing pending operations/handlers, which also destroys the stack context of the coro¹. Unwinding that stack destructs the (local) variables that live on that stack:
- handler
- result
- timer
And since timer
is not captured in the completion handler for async_wait
, the timer is simply canceled, but still tries to invoke the completion handler which now refers to a now-defunct stack variable handler
.
Copying handler
apparently² does keep the coro alive.
To answer the core question "and would like to test how an async function can be called" I'd suggest the simpler idiom:
void async_foo(boost::asio::io_service& io_service, boost::asio::yield_context& yield)
{
boost::asio::deadline_timer timer(io_service, boost::posix_time::seconds(1));
boost::system::error_code ec;
timer.async_wait(yield[ec]);
See it Live On Coliru
¹ -fsanitize=address
confirms this
² I know it contains a weak_ptr
to the coroutine context, so perhaps Asio internally locks that into a shared_ptr
, not too sure about those implementation details myself
来源:https://stackoverflow.com/questions/43403896/wondering-why-i-couldnt-just-capture-the-reference-of-asiohandler-type-for-th