问题描述
我惊讶地发现下面的代码无需将 io_context
作为第一个参数传递给 spawn
就可以运行。有人可以解释为什么在这种情况下我不需要通过它,以及在什么情况下你必须明确地通过它。 我使用的是 Boost 1.75.0。
#include <boost/asio/spawn.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <iostream>
int main() {
boost::asio::io_context io_context;
boost::asio::deadline_timer timer(io_context);
boost::asio::spawn([&](boost::asio::yield_context yield){ // don't need to pass io_context?!
std::cout << "started spawn" << std::endl;
timer.expires_from_Now(boost::posix_time::seconds(5));
timer.async_wait(yield);
std::cout << "finished spawn" << std::endl;
});
std::cout << "running io_context" << std::endl;
io_context.run();
std::cout << "finished running io_context" << std::endl;
}
解决方法
Asio 添加了关联执行器和默认执行器的概念。
相关的执行器并不是真正新的,因为 handler_invoke
协议已经允许处理程序类型的特定语义。然而,由于执行人概念的表述,它变得更加普遍。
现在您可以post
任何处理程序,它将在关联的执行程序上执行,执行程序提供或默认执行程序。默认执行器最终是 system_executor{}
。
所以
post([]{ puts("Hello world"); });
post(system_executor{},[]{ puts("Hello world"); });
两个处理程序都使用 system_executor
。
您可以将关联的处理程序与任何尚未关联的处理程序绑定:
post(bind_executor(ex1,[]{ puts("Hello world"); }));
post(system_executor{},bind_executor(ex1,[]{ puts("Hello world"); }));
两者都在 ex1
上运行处理程序,而不是后备。结合上面的内容,您已经可以期待这样做了:
post(ex1,[]{ puts("Hello world"); });
(这里处理程序没有关联执行器,所以 ex1
作为回退)
生成
Spawn 只是一个包装器,它“发布”了另一种类型的处理程序¹。事实上,使用任何关联的执行程序都有记录。实现很好地反映了这一点:
template <typename Function>
inline void spawn(BOOST_ASIO_MOVE_ARG(Function) function,const boost::coroutines::attributes& attributes)
{
typedef typename decay<Function>::type function_type;
typename associated_executor<function_type>::type ex(
(get_associated_executor)(function));
boost::asio::spawn(ex,BOOST_ASIO_MOVE_CAST(Function)(function),attributes);
}
您可以看到 get_associated_executor
的调用没有显式回退,再次默认为 system_executor
。
附注
另外
-
spawn
将在适当的地方添加一个链(这就是为什么在构建执行上下文时提供并发提示可以产生很大不同的原因) -
spawn
可以将yield_context
作为第一个参数,在这种情况下,您将有效地在同一条链上运行(共享执行程序)
¹ 这是一个实现细节,但通常是 boost::asio::detail::spawn_helper<...>
,它再次正确地传播关联的执行器/分配器。我将这种类型称为“处理程序绑定器”
现场演示
为了说明使用 system_executor
的实际情况,这里有一个简化的测试器:
#include <boost/asio/spawn.hpp>
#include <boost/asio/steady_timer.hpp>
#include <iostream>
int main() {
using namespace boost::asio;
using namespace std::chrono_literals;
io_context ctx(1);
spawn([](yield_context yield) {
std::cout << "started spawn" << std::endl;
auto ex = get_associated_executor(yield);
//auto work = make_work_guard(ex);
steady_timer timer(ex,5s);
timer.async_wait(yield);
std::cout << "finished spawn" << std::endl;
});
std::cout << "running context" << std::endl;
query(system_executor{},execution::context).join();
std::cout << "finished running context" << std::endl;
}
注意事项:
-
ctx
现在采用并发提示(如上所述) -
ctx
从未使用过;加入它不会等待 coro 完成! -
注意注释的
work
。重要的是,虽然异步操作构成了工作,但 Coro itself is not work 因此您可能希望在某些情况下保护 coro 的范围。 -
请注意,
system_executor
的连接方式与另一个基于线程的执行上下文(如thread_pool
)类似:query(system_executor{},execution::context).join();
现在打印
started spawn
running context
finished spawn
finished running context
按预期延迟 5 秒。