何时使用`asio_handler_invoke`?

时间:2015-09-30 03:08:02

标签: c++ boost-asio

问题

什么时候需要使用asio_handler_invoke来实现通过简单地包装处理程序无法完成的事情?

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

背景

提升asio文档包含如何使用asio_handler_invoke here的示例,但我不认为它是为什么的一个引人注目的例子您将使用调用处理程序。在该示例中,您似乎可以进行如下更改(并删除asio_handler_invoke)并获得相同的结果:

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

同样,在我与handler tracking相关的回答中,同样似乎没有必要使用asio_handler_invoke,尽管Tanner Sansbury's回答建议使用调用挂钩作为解决方案。

提升用户组上的

This thread提供了更多信息 - 但我不了解其重要性。

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

最后一点 - 我只从一个线程中调用io_service::run(),因此我可能会遗漏一些来自多线程循环经验的明显事物。

1 个答案:

答案 0 :(得分:29)

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

  • 包装处理程序以自定义处理程序的调用。
  • 定义asio_handler_invoke挂钩以在处理程序的上下文中自定义其他处理程序的调用。
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);

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

  

当异步操作由其他异步操作组成时,应使用与最终处理程序相同的方法调用所有中间处理程序。这是确保不以可能违反保证的方式访问用户定义的对象所必需的。这个[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);

在上面的代码段中,函数handle_readcounting_handler包裹。由于counting_handler对计算包装处理程序的次数不感兴趣,因此operator()不会增加计数并只调用handle_read。但是,counting_handlerasync_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;

async_read()组合操作将在零个或多个中间stream.async_read_some()操作中实现。对于这些中间操作中的每一个,将创建并调用具有未指定类型的处理程序。如果上述async_read()操作是根据2中间async_read_some()操作实施的,则counter.count()将为2,并且处理程序从{{1}返回被调用一次。

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

1

这是一个完整的示例demonstrating,用于计算执行的异步操作的数量,包括组合操作的中间操作。该示例仅启动三个异步操作(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. }; async_acceptasync_connect),但async_read操作将由async_read中间{{1}组成操作:

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; } 处理程序由一个包装两次的处理程序组成。首先是total operations: 4 read operations: 2 只计算读取操作,然后生成的仿函数由async_read()计算所有操作:

operation_counter

编写operation_counter boost::asio::async_read(..., all_operations.wrap(read_only.wrap(...))); 实现是为了通过在包装处理程序的上下文中调用Function来支持组合。这导致每个counting_handler发生适当的计数:

asio_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_); } ,则只调用最外层包装程序的调用策略。在这种情况下,它会导致asio_handler_invokefunction()all_operations.count()4

read_only.count()

在编写处理程序时,请注意调用的0挂钩位于argument-dependent lookup,因此它基于确切的处理程序类型。使用不template <typename Function> void asio_handler_invoke( Function function, counting_handler* context) { ++context->count_; function(); // No chaining. } 知道的类型组合处理程序将阻止调用策略的链接。例如,使用asio_handler_invokeasio_handler_invoke将导致调用默认的std::bind(),从而导致调用自定义调用策略:

std::function

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

当多个线程运行// Operations will not be counted. boost::asio::async_read(..., std::bind(all_operations.wrap(...))); 时,下面的代码片段可能会调用未定义的行为,因为两个组合操作的中间操作可能会同时运行,因为strand不会调用相应的io_service }:

std::bind()