草庐IT

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

coder 2023-06-03 原文

问题

什么时候需要使用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/

    有关c++ - 何时使用 `asio_handler_invoke`?的更多相关文章

    1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

      我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

    2. ruby - 使用 RubyZip 生成 ZIP 文件时设置压缩级别 - 2

      我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看ruby​​zip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d

    3. ruby - 为什么我可以在 Ruby 中使用 Object#send 访问私有(private)/ protected 方法? - 2

      类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc

    4. ruby-on-rails - 使用 Ruby on Rails 进行自动化测试 - 最佳实践 - 2

      很好奇,就使用ruby​​onrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提

    5. ruby - 在 Ruby 中使用匿名模块 - 2

      假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于

    6. ruby - 使用 ruby​​ 和 savon 的 SOAP 服务 - 2

      我正在尝试使用ruby​​和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我

    7. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

      关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

    8. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

      我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

    9. ruby - 使用 ruby​​ 将 HTML 转换为纯文本并维护结构/格式 - 2

      我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h

    10. ruby - 在 64 位 Snow Leopard 上使用 rvm、postgres 9.0、ruby 1.9.2-p136 安装 pg gem 时出现问题 - 2

      我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po

    随机推荐