何时使用`asio_handler_invoke`?

Zer*_*ero 13 c++ boost-asio

什么时候需要asio_handler_invoke通过简单地包装处理程序来实现无法实现的功能?

演示需要的情况的典型示例asio_handler_invoke将是理想的.

背景

boost asio docs包含了一个如何asio_handler_invoke 这里使用的例子,但我认为它不是一个令人信服的例子,说明为什么要使用调用处理程序.在该示例中,您似乎可以进行如下更改(并删除asio_handler_invoke)并获得相同的结果:

template <typename Arg1>
void operator()(Arg1 arg1)
{
  queue_.add(priority_, std::bind(handler_, arg1));
}
Run Code Online (Sandbox Code Playgroud)

同样,在我关于处理程序跟踪答案中asio_handler_invoke,尽管Tanner Sansbury的回答建议使用调用挂钩作为解决方案,但同样似乎没有必要使用它.

boost用户组上的这个线程提供了更多信息 - 但我不明白其意义.

从我所看到的,它似乎asio_handler_invoke总是被称为asio_handler_invoke(h, &h),似乎没有多大意义.在什么情况下,参数不是(基本上)相同对象的副本?

最后一点 - 我只是io_service::run()从一个线程调用,所以可能是我遗漏了一些来自多线程循环经验的明显东西.

Tan*_*ury 32

简而言之,包装处理程序并asio_handler_invoke完成两个不同的任务:

  • 包装处理程序以自定义处理程序的调用.
  • define asio_handler_invokehook,用于在处理程序的上下文中自定义其他处理程序的调用.
template <typename Handler>
struct custom_handler
{
  void operator()(...); // Customize invocation of handler_.
  Handler handler_;
};

// Customize invocation of Function within context of custom_handler.
template <typename Function>
void asio_handler_invoke(Function function, custom_handler* context);

// Invoke custom invocation of 'perform' within context of custom_handler.
void perform() {...}
custom_handler handler;
using boost::asio::asio_handler_invoke;
asio_handler_invoke(std::bind(&perform), &handler);
Run Code Online (Sandbox Code Playgroud)

asio_handler_invoke挂钩的主要原因是允许用户自定义应用程序可能无法直接访问的处理程序的调用策略.例如,考虑由对中间操作的零次或多次调用组成的组合操作.对于每个中间操作,将代表应用程序创建中间处理程序,但应用程序无法直接访问这些处理程序.使用自定义处理程序时,asio_handler_invoke钩子提供了一种在给定上下文中自定义这些中间处理程序的调用策略的方法.该文件规定:

当异步操作由其他异步操作组成时,应使用与最终处理程序相同的方法调用所有中间处理程序.这是确保不以可能违反保证的方式访问用户定义的对象所必需的.此[ asio_handler_invoke]挂钩函数确保在每个中间步骤都可以访问用于最终处理程序的调用方法.


asio_handler_invoke

考虑一种情况,我们希望计算执行的异步操作的数量,包括组合操作中的每个中间操作.为此,我们需要创建一个自定义处理程序类型counting_handler,并计算在其上下文中调用函数的次数:

template <typename Handler>
class counting_handler
{
  void operator()(...)
  {
    // invoke handler
  } 
  Handler handler_;
};

template <typename Function>
void asio_handler_invoke(Function function, counting_handler* context)
{
  // increment counter
  // invoke function
}

counting_handler handler(&handle_read);
boost::asio::async_read(socket, buffer, handler);
Run Code Online (Sandbox Code Playgroud)

在上面的代码片段中,函数handle_read被包装counting_handler.由于对counting_handler计算包装处理程序的次数不感兴趣,因此它operator()不会增加计数并只是调用handle_read.但是,counting_handler感兴趣的是在async_read操作的上下文中调用的处理程序数量,因此自定义调用策略asio_handler_invoke将增加计数.


这是基于上述counting_handler类型的具体示例.本operation_counter类提供了一种容易包装应用处理器有counting_handler:

namespace detail {

/// @brief counting_handler is a handler that counts the number of
///        times a handler is invoked within its context.
template <class Handler>
class counting_handler
{
public:
  counting_handler(Handler handler, std::size_t& count)
    : handler_(handler),
      count_(count)
  {}

  template <class... Args>
  void operator()(Args&&... args)
  {
    handler_(std::forward<Args>(args)...);
  }

  template <typename Function>
  friend void asio_handler_invoke(
    Function intermediate_handler,
    counting_handler* my_handler)
  {
    ++my_handler->count_;
    // Support chaining custom strategies incase the wrapped handler
    // has a custom strategy of its own.
    using boost::asio::asio_handler_invoke;
    asio_handler_invoke(intermediate_handler, &my_handler->handler_);
  }

private:
  Handler handler_;
  std::size_t& count_;
};

} // namespace detail

/// @brief Auxiliary class used to wrap handlers that will count
///        the number of functions invoked in their context.
class operation_counter
{
public:

  template <class Handler>
  detail::counting_handler<Handler> wrap(Handler handler)
  {
    return detail::counting_handler<Handler>(handler, count_);
  }

  std::size_t count() { return count_; }

private:
  std::size_t count_ = 0;
};

...

operation_counter counter;
boost::asio::async_read(socket, buffer, counter.wrap(&handle_read));
io_service.run();
std::cout << "Count of async_read_some operations: " <<
             counter.count() << std::endl;
Run Code Online (Sandbox Code Playgroud)

async_read()组成操作将在零个或多个中间来实现stream.async_read_some()操作.对于这些中间操作中的每一个,将创建并调用具有未指定类型的处理程序.如果上面的async_read()操作是在2中间async_read_some()操作方面实现的,那么counter.count()将是2,并且从counter.wrap()一次调用返回的处理程序.

另一方面,如果一个人没有提供一个asio_handler_invoke钩子而只是在包装的处理程序的调用中递增计数,那么计数将是1,仅反映包装的处理程序被调用的次数:

template <class Handler>
class counting_handler
{
public:
  ...

  template <class... Args>
  void operator()(Args&&... args)
  {
    ++count_;
    handler_(std::forward<Args>(args)...);
  }

  // No asio_handler_invoke implemented.
};
Run Code Online (Sandbox Code Playgroud)

这是一个完整的示例,演示计算执行的异步操作的数量,包括来自组合操作的中间操作.该示例仅启动三个异步操作(async_accept,async_connectasync_read),但async_read操作将由2中间async_read_some操作组成:

#include <functional> // std::bind
#include <iostream>   // std::cout, std::endl
#include <utility>    // std::forward
#include <boost/asio.hpp>

// This example is not interested in the handlers, so provide a noop function
// that will be passed to bind to meet the handler concept requirements.
void noop() {}

namespace detail {

/// @brief counting_handler is a handler that counts the number of
///        times a handler is invoked within its context.
template <class Handler>
class counting_handler
{
public:
  counting_handler(Handler handler, std::size_t& count)
    : handler_(handler),
      count_(count)
  {}

  template <class... Args>
  void operator()(Args&&... args)
  {
    handler_(std::forward<Args>(args)...);
  }

  template <typename Function>
  friend void asio_handler_invoke(
    Function function,
    counting_handler* context)
  {
    ++context->count_;
    // Support chaining custom strategies incase the wrapped handler
    // has a custom strategy of its own.
    using boost::asio::asio_handler_invoke;
    asio_handler_invoke(function, &context->handler_);
  }

private:
  Handler handler_;
  std::size_t& count_;
};

} // namespace detail

/// @brief Auxiliary class used to wrap handlers that will count
///        the number of functions invoked in their context.
class operation_counter
{
public:

  template <class Handler>
  detail::counting_handler<Handler> wrap(Handler handler)
  {
    return detail::counting_handler<Handler>(handler, count_);
  }

  std::size_t count() { return count_; }

private:
  std::size_t count_ = 0;
};

int main()
{
  using boost::asio::ip::tcp;
  operation_counter all_operations;

  // Create all I/O objects.
  boost::asio::io_service io_service;
  tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0));
  tcp::socket socket1(io_service);
  tcp::socket socket2(io_service);

  // Connect the sockets.
  // operation 1: acceptor.async_accept
  acceptor.async_accept(socket1, all_operations.wrap(std::bind(&noop)));
  // operation 2: socket2.async_connect
  socket2.async_connect(acceptor.local_endpoint(),
      all_operations.wrap(std::bind(&noop)));
  io_service.run();
  io_service.reset();

  // socket1 and socket2 are connected.  The scenario below will:
  // - write bytes to socket1.
  // - initiate a composed async_read operaiton to read more bytes
  //   than are currently available on socket2.  This will cause
  //   the operation to  complete with multple async_read_some 
  //   operations on socket2.
  // - write more bytes to socket1.

  // Write to socket1.
  std::string write_buffer = "demo";
  boost::asio::write(socket1, boost::asio::buffer(write_buffer));

  // Guarantee socket2 has received the data.
  assert(socket2.available() == write_buffer.size());

  // Initiate a composed operation to more data than is immediately
  // available.  As some data is available, an intermediate async_read_some
  // operation (operation 3) will be executed, and another async_read_some 
  // operation (operation 4) will eventually be initiated.
  std::vector<char> read_buffer(socket2.available() + 1);
  operation_counter read_only;
  boost::asio::async_read(socket2, boost::asio::buffer(read_buffer),
    all_operations.wrap(read_only.wrap(std::bind(&noop))));

  // Write more to socket1.  This will cause the async_read operation
  // to be complete.
  boost::asio::write(socket1, boost::asio::buffer(write_buffer));

  io_service.run();
  std::cout << "total operations: " << all_operations.count() << "\n"
               "read operations: " << read_only.count() << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

输出:

total operations: 4
read operations: 2
Run Code Online (Sandbox Code Playgroud)

组合处理程序

在上面的例子中,async_read()处理程序由一个包装两次的处理程序组成.首先,operation_counter这只是计算读取操作,然后生成的仿函数被operation_counter计数所有操作包装:

boost::asio::async_read(..., all_operations.wrap(read_only.wrap(...)));
Run Code Online (Sandbox Code Playgroud)

counting_handlerasio_handler_invoke实现是写在包裹处理程序的上下文的上下文调用功能,支持组成.这导致每个发生适当的计数operation_counter:

template <typename Function>
void asio_handler_invoke(
  Function function,
  counting_handler* context)
{
  ++context->count_;
  // Support chaining custom strategies incase the wrapped handler
  // has a custom strategy of its own.
  using boost::asio::asio_handler_invoke;
  asio_handler_invoke(function, &context->handler_);
}
Run Code Online (Sandbox Code Playgroud)

另一方面,如果asio_handler_invoke显式调用function(),则只调用最外层包装器的调用策略.在这种情况下,它将导致all_operations.count()存在4read_only.count()存在0:

template <typename Function>
void asio_handler_invoke(
  Function function,
  counting_handler* context)
{
  ++context->count_;
  function(); // No chaining.
}
Run Code Online (Sandbox Code Playgroud)

在编写处理程序时,请注意asio_handler_invoke调用的钩子是通过依赖参数的查找来定位的,因此它基于确切的处理程序类型.使用不asio_handler_invoke知道的类型组合处理程序将阻止调用策略的链接.例如,使用std::bind()std::function将导致asio_handler_invoke调用默认值,从而导致调用自定义调用策略:

// Operations will not be counted.
boost::asio::async_read(..., std::bind(all_operations.wrap(...)));    
Run Code Online (Sandbox Code Playgroud)

组合处理程序的正确链接调用策略非常重要.例如,返回的未指定的处理程序类型strand.wrap()提供了保证,由返回的处理程序的上下文中调用的包和函数调用的初始处理程序不会同时运行.这允许在使用组合操作时满足许多I/O对象的线程安全要求,因为它strand可以用于与应用程序无权访问的这些中间操作同步.

当运行io_service多个线程时,下面的代码段可能会调用未定义的行为,因为两个组合操作的中间操作可能会同时运行,因为std::bind()不会调用相应的asio_handler_hook:

boost::asio::async_read(socket, ..., std::bind(strand.wrap(&handle_read)));
boost::asio::async_write(socket, ..., std::bind(strand.wrap(&handle_write)));
Run Code Online (Sandbox Code Playgroud)

  • 出色的答案.但我不能不止一次地投票.所以我开始给这个消息一个赏金:*"一个或多个答案是典型的,**值得额外的赏金**."* (3认同)