c++ - When to use `asio_handler_invoke`? -
question
when necessary use asio_handler_invoke
achieve cannot done wrapping handler?
a canonical example demonstrates case asio_handler_invoke
required ideal.
background
the boost asio docs contain example of how use asio_handler_invoke
here, don't think compelling example of why use invocation handler. in example appears make changes following (and remove asio_handler_invoke
) , achieve identical result:
template <typename arg1> void operator()(arg1 arg1) { queue_.add(priority_, std::bind(handler_, arg1)); }
similarly, in answer relating handler tracking appears unnecessary use asio_handler_invoke
, despite tanner sansbury's answer suggesting using invocation hook solution.
this thread on boost user group provides more information - don't understand significance.
from have seen, appears asio_handler_invoke
called asio_handler_invoke(h, &h)
, doesn't appear make sense. in cases arguments not (essentially) copies of same object?
a final point - ever call io_service::run()
single thread, may i'm missing obvious comes experience in multi-threaded loop.
in short, wrapping handler , asio_handler_invoke
accomplish 2 different tasks:
- wrap handler customize invocation of handler.
- define
asio_handler_invoke
hook customize invocation of other handlers in context of 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);
the primary reason asio_handler_invoke
hook allow 1 customize invocation strategy of handlers application may not have direct access. instance, consider composed operations composed of 0 or more calls intermediate operations. each intermediate operation, intermediate handler created on behalf of application, application not have direct access these handlers. when using custom handlers, asio_handler_invoke
hook provides way customize invocation strategy of these intermediate handlers within given context. documentation states:
when asynchronous operations composed other asynchronous operations, intermediate handlers should invoked using same method final handler. required ensure user-defined objects not accessed in way may violate guarantees. [
asio_handler_invoke
] hooking function ensures invoked method used final handler accessible @ each intermediate step.
asio_handler_invoke
consider case wish count number of asynchronous operations executed, including each intermediate operation in composed operations. this, need create custom handler type, counting_handler
, , count number of times functions invoked within 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);
in above snippet, function handle_read
wrapped counting_handler
. counting_handler
not interested in counting number of times wrapped handler invoked, operator()
not increment count , invoke handle_read
. however, counting_handler
interested in amount of handlers invoked within context in async_read
operation, custom invocation strategy in asio_handler_invoke
increment count.
example
here concrete example based on above counting_handler
type. operation_counter
class provides way wrap application handlers counting_handler
:
namespace detail { /// @brief counting_handler handler counts number of /// times handler invoked within 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 wrapped handler // has custom strategy of 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 wrap handlers count /// number of functions invoked in 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;
the async_read()
composed operation implemented in 0 or more intermediate stream.async_read_some()
operations. each of these intermediate operations, handler unspecified type created , invoked. if above async_read()
operation implemented in terms of 2
intermediate async_read_some()
operations, counter.count()
2
, , handler returned counter.wrap()
got invoked once.
on other hand, if 1 not provide asio_handler_invoke
hook , instead incremented count within wrapped handler's invocation, count 1
, reflecting count of times wrapped handler 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. };
here complete example demonstrating counting number of asynchronous operations executed, including intermediate operations composed operation. example initiates 3 async operations (async_accept
, async_connect
, , async_read
), async_read
operation 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> // example not interested in handlers, provide noop function // passed bind meet handler concept requirements. void noop() {} namespace detail { /// @brief counting_handler handler counts number of /// times handler invoked within 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 wrapped handler // has custom strategy of 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 wrap handlers count /// number of functions invoked in 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 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 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 , socket2 connected. scenario below will: // - write bytes socket1. // - initiate composed async_read operaiton read more bytes // available on socket2. cause // operation complete multple async_read_some // operations on socket2. // - write more bytes socket1. // write socket1. std::string write_buffer = "demo"; boost::asio::write(socket1, boost::asio::buffer(write_buffer)); // guarantee socket2 has received data. assert(socket2.available() == write_buffer.size()); // initiate composed operation more data // available. data available, intermediate async_read_some // operation (operation 3) executed, , async_read_some // operation (operation 4) 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 socket1. cause async_read operation // 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; }
output:
total operations: 4 read operations: 2
composed handlers
in above example, async_read()
handler composed of handler wrapped twice. first operation_counter
counting read operations, , resulting functor wrapped operation_counter
counting operations:
boost::asio::async_read(..., all_operations.wrap(read_only.wrap(...)));
the counting_handler
's asio_handler_invoke
implementation written support composition invoking function in context of wrapped handler's context. results in appropriate counting occurred each operation_counter
:
template <typename function> void asio_handler_invoke( function function, counting_handler* context) { ++context->count_; // support chaining custom strategies incase wrapped handler // has custom strategy of own. using boost::asio::asio_handler_invoke; asio_handler_invoke(function, &context->handler_); }
on other hand, if asio_handler_invoke
explicitly called function()
, outer wrapper's invocation strategy invoked. in case, result in all_operations.count()
being 4
, read_only.count()
being 0
:
template <typename function> void asio_handler_invoke( function function, counting_handler* context) { ++context->count_; function(); // no chaining. }
when composing handlers, aware asio_handler_invoke
hook gets invoked located through argument-dependent lookup, based on exact handler type. composing handlers types not asio_handler_invoke
aware prevent chaining of invocation strategies. instance, using std::bind()
or std::function
result in default asio_handler_invoke
being called, causing custom invocation strategies being invoked:
// operations not counted. boost::asio::async_read(..., std::bind(all_operations.wrap(...)));
proper chaining invocation strategies composed handlers can important. example, unspecified handler type returned strand.wrap()
provides guarantee initial handler wrapped strand , functions invoked in context of returned handler not run concurrently. allows 1 meet thread-safety requirements of many of i/o objects when using composed operations, strand
can used synchronize these intermediate operations application not have access.
when running io_service
multiple threads, below snippet may invoke undefined behavior, intermediate operations both composed operations may run concurrently, std::bind()
not invoke 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)));
Comments
Post a Comment