微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

什么时候必须将 io_context 传递给 boost::asio::spawn? (C++)

如何解决什么时候必须将 io_context 传递给 boost::asio::spawn? (C++)

我惊讶地发现下面的代码无需将 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 的实际情况,这里有一个简化的测试器:

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 从未使用过;加入它不会等待 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 秒。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。