什么时候使用`asio_handler_invoke`? [英] When to use `asio_handler_invoke`?

查看:485
本文介绍了什么时候使用`asio_handler_invoke`?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

何时需要使用 asio_handler_invoke

一个规范的例子演示了一个 asio_handler_invoke 是必需的理想。

A canonical example that demonstrates a case where asio_handler_invoke is required would be ideal.

背景

boost asio文档包含 > 如何使用 asio_handler_invoke 在这里,但我不认为这是一个引人注目的例子为什么你会使用调用处理程序。在这个例子中,你可以进行如下改变(并删除 asio_handler_invoke )并获得相同的结果:

The boost asio docs contain an example of how to use asio_handler_invoke here, but I don't think it is a compelling example of why you would use the invocation handler. In that example it appears you could make changes like the following (and remove the asio_handler_invoke) and achieve an identical result:

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

同样,在我的回答中, handler跟踪类似地似乎没有必要使用 asio_handler_invoke ,尽管 Tanner Sansbury的回答建议使用调用钩子作为解决方案。

Similarly, in my answer relating to handler tracking it similarly appears unnecessary to use asio_handler_invoke, despite Tanner Sansbury's answer suggesting using the invocation hook as a solution.

boost用户组上的此线程提供了一些更多信息 - 但我不明白意义。

This thread on the boost user group provides some more information - but I don't understand the significance.

从我所看到的,看起来 asio_handler_invoke 总是被称为 asio_handler_invoke(h,& h),这似乎没有什么意义。

From what I have seen, it appears asio_handler_invoke is always called like asio_handler_invoke(h, &h), which doesn't appear to make much sense. In what cases would the arguments not be (essentially) copies of the same object?

最后一点 - 我只是调用 io_service :: run ()从一个单一的线程,所以它可能是我缺少一个明显的来自多线程循环的经验。

A final point - I only ever call io_service::run() from a single thread, so it may be I'm missing something obvious that comes from experience in a multi-threaded loop.

推荐答案

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

In short, wrapping a handler and asio_handler_invoke accomplish two different tasks:


  • 包装处理程序以自定义处理程序的调用。

  • define asio_handler_invoke

  • wrap a handler to customize the invocation of a handler.
  • define asio_handler_invoke hook to customize the invocation of other handlers in the context of a handler.
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 钩子提供了一种在给定上下文中定制这些中间处理程序的调用策略的方法。 文档说明:

The primary reason for the asio_handler_invoke hook is to allow one to customize the invocation strategy of handlers to which the application may not have direct access. For instance, consider composed operations that are composed of zero or more calls to intermediate operations. For each intermediate operation, an intermediate handler will be created on behalf of the application, but the application does not have direct access to these handlers. When using custom handlers, the asio_handler_invoke hook provides a way to customize the invocation strategy of these intermediate handlers within a given context. The documentation states:


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

When asynchronous operations are composed from other asynchronous operations, all intermediate handlers should be invoked using the same method as the final handler. This is required to ensure that user-defined objects are not accessed in a way that may violate the guarantees. This [asio_handler_invoke] hooking function ensures that the invoked method used for the final handler is accessible at each intermediate step.






asio_handler_invoke



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


asio_handler_invoke

Consider a case where we wish to count the number of asynchronous operations executed, including each intermediate operation in composed operations. To do this, we need to create a custom handler type, counting_handler, and count the number of times functions are invoked within its context:

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_read counting_handler 包装。由于 counting_handler 对计算包装处理程序的调用次数不感兴趣,因此 operator()不会增加计数,只需调用 handle_read 。然而, counting_handler 对在 async_read 操作中在其上下文中调用的处理程序数量感兴趣,因此自定义调用 asio_handler_invoke 中的策略将增加计数。

In the above snippet, the function handle_read is wrapped by counting_handler. As the counting_handler is not interested in counting the number of times the wrapped handler is invoked, its operator() will not increment the count and just invoke handle_read. However, the counting_handler is interested in the amount of handlers invoked within its context in the async_read operation, so the custom invocation strategy in asio_handler_invoke will increment a count.

下面是一个基于上述 counting_handler 类型的具体示例。 operation_counter 类提供了一种方法,可以轻松地用 counting_handler 包装应用程序处理程序:

Here is a concrete example based on the above counting_handler type. The operation_counter class provides a way to easily wrap application handlers with a 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 / code>操作,则 counter.count()将为 2 code> counter.wrap()调用一次。

The async_read() composed operation will be implemented in zero or more intermediate stream.async_read_some() operations. For each of these intermediate operations, a handler with an unspecified type will be created and invoked. If the above async_read() operation was implemented in terms of 2 intermediate async_read_some() operations, then counter.count() will be 2, and the handler returned from counter.wrap() got invoked once.

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

On the other hand, if one were to not provide an asio_handler_invoke hook and instead only incremented the count within the wrapped handler's invocation, then the count would be 1, reflecting only the count of times the wrapped handler was invoked:

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_accept async_connect async_read ),但是 async_read 操作将由 2 中间 async_read_some 操作:


Here is a complete example demonstrating counting the number of asynchronous operations that get executed, including intermediate operations from a composed operation. The example only initiates three async operations (async_accept, async_connect, and async_read), but the async_read operation will be composed of 2 intermediate async_read_some operations:

#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 操作:

Composed Handlers

In the above example, the async_read() handler was composed of a handler wrapped twice. First by the operation_counter that is only counting for read operations, and then the resulting functor was wrapped by the operation_counter counting all operations:

boost::asio::async_read(..., all_operations.wrap(read_only.wrap(...)));

counting_handler asio_handler_invoke 实现被编写为通过在包装的处理程序的上下文中调用函数来支持组合。这会导致每个 operation_counter 发生适当的计数:

The counting_handler's asio_handler_invoke implementation is written to support composition by invoking the Function in the context of the wrapped handler's context. This results in the appropriate counting occurred for each 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_invoke 显式调用 function(),那么只有最外层的调用策略会被调用。在这种情况下,会导致 all_operations.count() 4 read_only。 count() 0

On the other hand, if the asio_handler_invoke explicitly called function(), then only the outer most wrapper's invocation strategy would be invoked. In this case, it would result in all_operations.count() being 4 and read_only.count() being 0:

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

在构造处理程序时,请注意 asio_handler_invoke 被调用的钩子位于基于参数的查找中,因此它基于精确处理程序类型。不具有 asio_handler_invoke 意识的类型的编译处理程序将阻止调用策略的链接。例如,使用 std :: bind() std :: function 将导致默认 asio_handler_invoke 被调用,导致调用自定义调用策略:

When composing handlers, be aware that the asio_handler_invoke hook that gets invoked is located through argument-dependent lookup, so it is based on the exact handler type. Composing handlers with types that are not asio_handler_invoke aware will prevent the chaining of invocation strategies. For instance, using std::bind() or std::function will result in the default asio_handler_invoke being called, causing custom invocation strategies from being invoked:

// Operations will not be counted.
boost::asio::async_read(..., std::bind(all_operations.wrap(...)));    

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

Proper chaining invocation strategies for composed handlers can be very important. For example, the unspecified handler type returned from strand.wrap() provides the guarantee that initial handler wrapped by the strand and functions invoked in the context of the returned handler will not run concurrently. This allows one to meet the thread-safety requirements of many of the I/O objects when using composed operations, as the strand can be used to synchronize with these intermediate operations to which the application does not have access.

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

When running the io_service by multiple threads, the below snippet may invoke undefined behavior, as the intermediate operations for both composed operations may run concurrently, as std::bind() will not invoke the appropriate 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)));

这篇关于什么时候使用`asio_handler_invoke`?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆