gpt4 book ai didi

c++ - 何时使用 `asio_handler_invoke`?

转载 作者:IT老高 更新时间:2023-10-28 23:09:49 26 4
gpt4 key购买 nike

问题

什么时候需要使用asio_handler_invoke来实现仅通过包装处理程序无法完成的操作?

一个规范的示例说明需要asio_handler_invoke的情况将是理想的。

背景

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

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

类似地,在我有关 handler tracking的答案中,尽管 Tanner Sansbury's答案建议使用调用挂钩作为解决方案,但同样似乎不需要使用 asio_handler_invoke

Boost用户组上的 This thread提供了更多信息-但我不了解其重要性。

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

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

最佳答案

简而言之,包装处理程序和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指出:

    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,并计算在其上下文中调用函数的次数:

    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中的自定义调用策略将增加一个计数。



    这是基于上述 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,并且从 counter.wrap()返回的处理程序将被调用一次。

    另一方面,如果不提供 asio_handler_invoke钩子(Hook),而是仅在包装的处理程序调用内增加计数,则该计数为 1,仅反射(reflect)包装的处理程序被调用的次数:

    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.
    };

    这是一个完整的示例 demonstrating,它计算要执行的异步操作的数量,包括来自组合操作的中间操作。该示例仅启动三个异步操作( async_acceptasync_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;
    }

    输出:

    total operations: 4
    read operations: 2

    组合处理程序

    在上面的示例中, async_read()处理程序由包装两次的处理程序组成。首先由仅计数读取操作的 operation_counter组成,然后由 operation_counter包装计数所有操作的结果函子:

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

    通过在包装的处理程序上下文中调用Function来编写 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_);
    }

    另一方面,如果 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.
    }

    在编写处理程序时,请注意,被调用的 asio_handler_invoke挂钩是通过 argument-dependent lookup定位的,因此它基于确切的处理程序类型。使用不支持 asio_handler_invoke的类型来组合处理程序将防止调用策略的链接。例如,使用 std::bind()std::function将导致调用默认的 asio_handler_invoke,从而导致调用自定义调用策略:

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

    对于组合处理程序,正确的链接调用策略可能非常重要。例如,从 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)));

    关于c++ - 何时使用 `asio_handler_invoke`?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/32857101/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com