什么时候必须通过io_context来增强boost :: asio :: spawn?(C ++) [英] When must you pass io_context to boost::asio::spawn? (C++)
问题描述
我很惊讶地发现下面的代码在没有将 io_context
作为 spawn
的第一个参数的情况下有效.有人可以解释一下为什么在这种情况下我不需要通过它,以及在什么情况下您必须显式地通过它.我正在使用Boost 1.75.0 .
I was surprised to find that the code below works without passing io_context
as the first argument to spawn
. Could somebody please explain why I don't need to pass it in this case, and in what situations you must explicitly pass it. I'm using 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添加了关联执行程序和默认执行程序的概念.
Asio has added the concept of associated executors and default executors.
关联的执行程序并不是真正的新事物,因为 handler_invoke
协议已经允许使用特定于处理程序类型的语义.但是,自从执行者概念的提出以来,它就变得更加普遍了.
The associated executors is not really new, because the handler_invoke
protocol already allowed for handler-type specific semantics. However, since the formulation of the executor concept it became more generalized.
现在,您可以 post
任何处理程序,它将在关联的执行程序(提供的执行程序或默认执行程序)上执行.默认执行程序最终是 system_executor {}
.
Now you can post
any handler, and it will execute on the associated executor, the executor supplied or the default executor. The default executor is ultimately system_executor{}
.
所以
post([]{ puts("Hello world"); });
post(system_executor{}, []{ puts("Hello world"); });
两个处理程序都使用 system_executor
.
您可以将关联的处理程序与尚未与之关联的任何处理程序绑定:
You can bind an associated handler with any handler that doesn't associate one already:
post(bind_executor(ex1, []{ puts("Hello world"); }));
post(system_executor{}, bind_executor(ex1, []{ puts("Hello world"); }));
两者都在 ex1
上运行处理程序,而不是后备程序.结合以上内容,您将已经期望这样做:
Both run the handler on ex1
, not the fallbacks. Combining the above, you will already expect that this does the same:
post(ex1, []{ puts("Hello world"); });
(此处,处理程序没有关联的执行程序,因此 ex1
作为后备功能)
(here, the handler has not associated executor, so ex1
functions as the fallback)
Spawn只是一个发布"到目录的包装器.另一种处理程序¹.实际上,已证明可以使用任何关联的执行程序.该实现非常清晰地反映了这一点:
Spawn is merely a wrapper that "posts" another type of handler¹. Indeed it is documented to use any associated executor. The implementation reflects this quite readably:
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
.
You can see that get_associated_executor
is called without explicit fallback, defaulting to system_executor
again.
另外
-
spawn
将在适当的地方添加一个子链(这就是为什么在构造执行上下文时提供并发提示会产生很大的不同的原因) -
spawn
可以将yield_context
作为第一个参数,在这种情况下,您将有效地在同一链上运行(共享执行程序)
spawn
will add a strand where appropriate (this is a reason why providing a concurrency hint when constructing your execution context can make a big difference)spawn
can take ayield_context
as the first argument, in which case you will effectively run on the same strand (sharing the executor)
¹这是一个实现细节,但通常为 boost :: asio :: detail :: spawn_helper< ...>
,可以再次正确传播关联的执行者/分配器.我将这种类型称为处理程序绑定程序"
¹ It's an implementation detail, but it will generally be boost::asio::detail::spawn_helper<...>
which correctly propagates associated executors/allocators again. I would dub this kind of type a "handler binder"
为说明使用 system_executor
的现实情况,下面是一个简化的测试器:
To illustrate the reality that system_executor
is being used, here's a simplified tester:
Compiler Explorer
#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
now takes a concurrency hint (as mentioned above)
ctx
从未使用;加入它会不等待Coro完成!
ctx
is never used; joining it would not wait for coro to be completed!
请注意注释的作品
.重要的是,尽管异步操作可以正常工作,但 Coro本身不起作用,因此您可能需要注意在某些情况下是冠冕.
Note the commented work
. It is important that though async operations constitute work, the Coro itself is not work so you might want to guard the scope of the coro in some situations.
请注意,像连接其他基于线程的执行上下文(如 thread_pool
:
Note that system_executor
is joined like you would another thread-based execution context like thread_pool
:
query(system_executor{}, execution::context).join();
现在可以打印
started spawn
running context
finished spawn
finished running context
延迟5s.
这篇关于什么时候必须通过io_context来增强boost :: asio :: spawn?(C ++)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!