在尝试
boost::asio::awaitable
和 Executor
时,我不断观察到一些相当令人困惑的行为,我想更好地理解这些行为
请看以下节目:
int main(int argc, char const* args[])
{
boost::asio::io_context ioc;
auto spawn_strand = boost::asio::make_strand(ioc);
boost::asio::co_spawn(spawn_strand,
[&]() -> boost::asio::awaitable<void>
{
auto switch_strand = boost::asio::make_strand(ioc);
co_await boost::asio::post(switch_strand, // (*)
boost::asio::bind_executor(switch_strand, boost::asio::use_awaitable));
boost::asio::post(spawn_strand, [](){
std::cout << "calling handler\n";
});
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "waking up\n";
},
boost::asio::detached);
std::jthread threads[3]; // provide enough threads to serve strands in parallel
for (auto& thread : threads)
thread = std::jthread{ [&]() { ioc.run(); }};
}
正如预期的那样,该程序输出:
calling handler
waking up
具体来说,
"calling handler"
在"waking up"
之前打印,因为在(*)
行之后,协程不再在spawn_strand
上运行。因此,后者将不会被sleep_for
阻止,并且可以立即运行处理程序。
到目前为止一切顺利,现在让我们揭示一些令人困惑的行为......
事实证明,
assert
ing spawn_strand == co_await boost::asio::this_coro::executor
after行(*)
确实not失败。我们假设此时执行切换到了switch_strand
,实际上只是证实了这一事实。因此,此时我希望 co_await boost::asio::this_coro::executor
比较等于 switch_strand
而不是 spawn_strand
。
如果在
(*)
行中,我们将传递给 post
的链替换为 spawn_strand
,则输出将更改为:
waking up
calling handler
我已经阅读了有关提供给 post
和 bind_executor
的执行器的其他答案(例如
this或
this),并且通常他们建议前者仅作为后备,以防处理程序不关联的处理程序。这与观察到的输出不匹配。
现在,让我们对前一段进行更改并添加
boost::asio::post(switch_strand, [](){
std::cout << "calling handler\n";
});
在
(*)
行之后。请注意,这次我们使用 switch_strand
而不是 spawn_strand
。我们将得到以下输出
waking up
calling handler
calling handler
这表明两个处理程序(在
switch_strand
上以及spawn_strand
上)都被单个sleep_for
阻止。最终,看起来在 (*)
行之后,协程同时在 both 线上运行,这非常令人恼火。
要点 2. 和 3. 同样适用,我们不是替换传递到
post
的支架,而是用 bind_handler
替换传递到 spawn_strand
的股线。
如何解释这些奇怪的观察结果?在我看来,除了执行器调用处理程序的通常功能之外,
boost::asio::awaitable
还与一个在执行过程中永久存在的附加执行器(即提供给co_spawn
并可通过this_coro::executor
访问的执行器)相关联。协程的。然而,我还没有在任何地方找到任何解释;既不是 Boost.Asio C++20 Coroutines Support 的文档,也不是此处相关问题的答案。所以我不相信事情实际上是这样运作的。
assert(spawn_strand == co_await asio::this_coro::executor);
是同义反复。你确实在链上生成了科罗,所以它将
是科罗链。您可能想要检查的内容:
assert(spawn_strand.running_in_this_thread());
这将会
在(*)
线之后失败
的后备。 未绑定执行者时使用。当你裸体发帖时 lambda,就是这样。
链接的答案主要涉及以下事实:
asio::use_awaitable
确实将coro的执行器作为其关联的执行器,这意味着显式执行器将不会用于完成,除非被例如覆盖。
bind_executor
。
最终看起来像是在 (*) 行之后,协程同时在两条链上运行,这非常令人恼火。
好消息:您可以处于多种状态(并且有充分的理由,例如,如果您需要同步访问多个资源)。
更好地认识到任何协程根据定义都是它自己的逻辑链。事实上,
stackful coroutines明确地将它们的执行器包装在一个链中。 C++20 协程可能不必这样做,但语言规范指定以保证 coro 主体顺序执行的方式恢复。