想知道为什么我不能只捕获协程的 asio::handler_type 的引用



我正在使用协程与asio一起玩,并想测试如何调用异步函数。我有以下代码:

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

奇怪的是,如果我将 &handler 放在捕获列表中,执行流程会搞砸,然后遇到分段错误。但是如果我使用"处理程序"代替,它可以运行没有任何问题(那么我当然需要在 lambda 中复制一个副本)。

四处搜索,找不到任何相关内容。提前感谢任何帮助。

正如Tanner在这里很好地解释的那样:

  • 虽然spawn()将工作添加到io_service(将启动并跳转到的处理程序) 协程),协程本身不起作用。 为了防止io_service在协程未完成时结束的事件循环, 在屈服之前,可能需要向io_service添加功。

如果在run()后添加额外的跟踪线:

io_service.run();
std::cout << "BYE" << std::endl;

然后,运行几次,直到您足够幸运地没有提前获得 SEGV,您可以看到如下所示的输出:

hello
enter
done
BYE

实际上,io_service会返回,破坏挂起的操作/处理程序,这也破坏了coro¹的堆栈上下文。展开该堆栈会破坏该堆栈上的(局部)变量:

  • 处理器
  • 结果
  • 定时器

由于timer没有在async_wait的完成处理程序中捕获,计时器只是被取消,但仍尝试调用完成处理程序,该处理程序现在引用现已失效的堆栈变量handler

显然,复制handler²确实使coro保持活力。


为了回答核心问题"并想测试如何调用异步函数">,我建议使用更简单的习语:

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]);

科里鲁现场观看


¹-fsanitize=address确认这一点 ² 我知道它包含对协程上下文的weak_ptr,所以也许 Asio 在内部将其锁定到shared_ptr中,我自己不太确定这些实现细节

最新更新