【问题标题】:boost::asio::spawn yield as callbackboost::asio::spawn 收益率作为回调
【发布时间】:2014-08-21 06:46:41
【问题描述】:

我正在尝试使用 boost::asio::spawn 协程重写一个项目。项目的某些部分无法更改。比如存储协议库也是用boost::asio写的,但是没有协程。

问题是如何将yield_context 转换为普通回调(boost::function 对象或经典函子)。

这就是我们在存储库 API 中的内容:

void async_request_data(uint64_t item_id, boost::function< void(Request_result *) > callback);

从示例中我们知道,asio yield 上下文可以这样使用:

    my_socket.async_read_some(boost::asio::buffer(data), yield);

在这种情况下,boost::asio::yield_context 对象用作 async_read_some 的回调。我想将 yield 对象作为第二个参数传递给async_request_data,这样我就可以以同步的方式使用它。

如何做到这一点?我认为可以通过一些代理对象实现,可能使用基于 asio_handler_invoke 的方法。但我很难看到如何做到这一点。

【问题讨论】:

    标签: c++ boost boost-asio coroutine


    【解决方案1】:

    看起来这个特性的最佳文档可以在 boost asio 作者编写的 C++ 标准提案中找到:

    N4045 – Library Foundations for Asynchronous Operations, Revision 2

    请参阅第 9.1 节:

    handler_type_t<CompletionToken, void(error_code, size_t)>   #3
      handler(std::forward<CompletionToken>(token));
    

    3:完成标记被转换为处理程序,即要调用的函数对象 当异步操作完成时。签名指定参数 这将被传递给处理程序。

    我猜在你的情况下,CompletionToken 模板参数实际上是boost::asio::yield_contexthandler_type 将其转换为回调对象。


    这是第 9.1 节中更新的代码以调用您的 async_request_data 函数:

    template <class CompletionToken>
    auto async_foo(uint64_t item_id, CompletionToken&& token)
    {
      handler_type_t<CompletionToken, void(Request_result *)>
        handler(std::forward<CompletionToken>(token));
    
      async_result<decltype(handler)> result(handler);  
    
      async_request_data(item_id, handler);
    
      return result.get();  
    }
    

    【讨论】:

    • 当然,我需要一些代理对象将其作为回调传递,但不清楚如何编写该对象的内容。 yield_context 没有 operator()(否则它会在没有代理的情况下“按原样”工作)。它有一些在这里描述的胆量boost.org/doc/libs/1_54_0/doc/html/boost_asio/reference/…,但不清楚如何将它们结合起来做一个正确的协程恢复。
    • 我认为handler 代理对象。请参阅编辑。
    • 它做了一些小改动!谢谢指点! (我在另一个答案中发布了最终代码)
    • 谢谢@free_coffee 和@PSIAlt!这个技巧将允许我即将推出的基于 Asio 的库提供处理程序和基于协程的 API,而无需两次实现所有内容!
    【解决方案2】:

    感谢@PSIAlt 和@free_coffee,我知道如何在堆栈式协程中使用回调函数。

    这是一个简单的示例,仅适用于 asio 新手(比如我:D)

    https://gist.github.com/chenfengyuan/4d764b0bca82a42c05a9

    #include <iostream>
    #include <boost/asio.hpp>
    #include <boost/date_time/posix_time/posix_time.hpp>
    #include <boost/asio/spawn.hpp>
    #include <memory>
    
    void bar(boost::asio::io_service &io, std::function<void()> cb){
        auto ptr = std::make_shared<boost::asio::deadline_timer>(io, boost::posix_time::seconds(1));
        ptr->async_wait([ptr, cb](const boost::system::error_code&){cb();});
    }
    
    template<typename Handler>
    void foo(boost::asio::io_service &io, Handler && handler){
        typename boost::asio::handler_type<Handler, void()>::type handler_(std::forward<Handler>(handler));
        boost::asio::async_result<decltype(handler_)> result(handler_);
        bar(io, handler_);
        result.get();
        return;
    }
    
    int main()
    {
      boost::asio::io_service io;
      boost::asio::spawn(io, [&io](boost::asio::yield_context yield){
          foo(io, yield);
          std::cout << "hello, world!\n";
      });
    
      io.run();
    
      return 0;
    }
    

    【讨论】:

    • 不错的小例子!有一个微妙的问题。 handler_ 必须通过 asio_handler_invoke() 钩子调用才能与协程正确同步。否则,在多线程环境中,可能会出现竞态条件,即协程在产生之前尝试恢复。 asio_handler_invoke 钩子针对特定类型进行了重载,因此不会发生对 std::function&lt;&gt; 的类型擦除。 Here 是一个更新的解决方案。
    【解决方案3】:

    非常感谢 free_coffe 我成功地完成了这项工作。为我的案例发布解决方案,可能有人需要它。

    template <class CompletionToken>
    RequestResult async_foo(Packet &pkt, CompletionToken&& token) {
       typename boost::asio::handler_type< CompletionToken, void(RequestResult) >::type handler( std::forward<CompletionToken>(token) );
      boost::asio::async_result<decltype(handler)> result(handler);
      storage_api->writePacket(pkt, handler);
      return result.get();
    }
    

    稍后我们可以使用这个代理:

    RequestResult res = async_foo(pkt, std::forward<boost::asio::yield_context>(yield) );
    

    【讨论】:

    • 太棒了!不过,使用detail 类闻起来不太对劲。也许您可以发布尝试使用 handler_type 时的错误?
    • @free_coffee error: could not convert ‘result.boost::asio::async_result&lt;Handler&gt;::get&lt;boost::asio::basic_yield_context&lt;boost::asio::detail::wrapped_handler&lt;boost::asio::io_service::strand, void (*)(), boost::asio::detail ::is_continuation_if_running&gt; &gt; &gt;()’ from ‘boost::asio::async_result&lt;boost::asio::basic_yield_context&lt;boost::asio::detail::wrapped_handler&lt;boost::asio::io_service::strand, void (*)(), boost::asio::detail::is_continuation_if_running&gt; &gt; &gt;: :type {aka void}’ to ‘RequestResult’
    • 看起来yield_context 以某种方式作为async_result 模板参数传递,这意味着decltype(handler) 不是它应该是的。能发一下代码吗?
    • @free_coffee 当然,这是gist.github.com/PSIAlt/d4c9ccf48b962f797efd
    • 我认为handler_type 的第二个模板参数应该是一个函数签名,所以handler_type&lt;CompletionToken, void(RequestResult)&gt; 而不是handler_type&lt;CompletionToken, RequestResult&gt;
    猜你喜欢
    • 2015-08-13
    • 1970-01-01
    • 2010-12-21
    • 2011-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-18
    • 1970-01-01
    相关资源
    最近更新 更多