【问题标题】:how to use boost::asio::defer() in composing function?如何在组合函数中使用 boost::asio::defer()?
【发布时间】:2018-05-04 13:53:17
【问题描述】:

在 Boost 1.66 上,Asio 具有 deprecatedasio_handler_is_continuation 挂钩函数,促进了 defer 函数的使用。当 asio_handler_is_continuation==true 时,It seems thatdefer 函数的行为与 post 完全相同。但是defer的使用方式与asio_handler_is_continuation的使用方式不同,我不知道如何正确使用defer

编辑:我认为下面的示例过于冗长,无法清楚地表达我的意思。这是更短的示例:

async_read_until(stream, read_buffer, "\r\n", 
    [](boost::system::error_code ec, std::size_t bytes_transferred)
    {
        if(!ec)
            async_write(stream, write_buffer, some_handler);
    })

现在当async_read_until 完成时,传递的lambda 处理程序将使用与boost::asio::post 等效的方式调用。但是 lambda 处理程序中的 async_write 是上一个异步任务的延续,所以我想使用 defer 调用 lambda 处理程序以利用优化。

有没有办法使用defer(而不是post)来调用上面示例中的lambda处理程序?

ORIGINAL POST:我正在尝试编写一个简单的启动函数async_echo,类似于beast document 中的那个,除了调用boost::asio::async_write 的部分将被称为延续。为此,之前的中间操作boost::asio::async_read_until 必须调用处理程序*this 作为延续。

这是我在野兽文档的 async_echo 示例中所指的部分:

template<class AsyncStream, class Handler>
void echo_op<AsyncStream, Handler>::
operator()(boost::beast::error_code ec, std::size_t bytes_transferred)
{
    // Store a reference to our state. The address of the state won't
    // change, and this solves the problem where dereferencing the
    // data member is undefined after a move.
    auto& p = *p_;

    // Now perform the next step in the state machine
    switch(ec ? 2 : p.step)
    {
        // initial entry
        case 0:
            // read up to the first newline
            p.step = 1;
            return boost::asio::async_read_until(p.stream, p.buffer, "\r", std::move(*this));

        case 1:
            // write everything back
            p.step = 2;
            // async_read_until could have read past the newline,
            // use buffers_prefix to make sure we only send one line
            return boost::asio::async_write(p.stream,
                boost::beast::buffers_prefix(bytes_transferred, p.buffer.data()), std::move(*this));

        case 2:
            p.buffer.consume(bytes_transferred);
            break;
    }

    // Invoke the final handler. The implementation of `handler_ptr`
    // will deallocate the storage for the state before the handler
    // is invoked. This is necessary to provide the
    // destroy-before-invocation guarantee on handler memory
    // customizations.
    //
    // If we wanted to pass any arguments to the handler which come
    // from the `state`, they would have to be moved to the stack
    // first or else undefined behavior results.
    //
    p_.invoke(ec);
    return;
}

在 1.66 之前的日子里,我可以简单地挂钩函数如下:

template <Function, Handler>
friend bool asio_handler_is_continuation(echo_op<Function, Handler>* handler)
{
    using boost::asio::asio_handler_is_continuation;
    return handler.p_->step == 1 || 
        asio_handler_is_continuation(std::addressof(handler.p_->handler()));
}

echo_op的声明中。

从 Boost 1.66 开始,上面的代码不太可能有任何效果(没有BOOST_ASIO_NO_DEPRECATION 宏)。所以我应该使用defer

但由于boost::asio::async_read_until 具有a guarantee,“处理程序的调用将以等效于使用 boost::asio::io_context::post() 的方式执行。”,*this 将不会被调用使用defer,即作为延续。

是否有任何解决方法使boost::asio::async_read_until 使用defer 调用处理程序?有没有利用defer函数的好例子?

【问题讨论】:

  • 我也想知道这个问题的答案。而你的问题恰恰反映了我对此事的看法。 asio_handler_is_continuation 的语义不同于 postdefer 的使用。
  • @VinnieFalco 在这种情况下我们必须使用asio_handler_is_continuation。看我的回答。

标签: c++ boost boost-asio boost-beast


【解决方案1】:

这在过去也让我感到困惑。

Executor::deferExecutor::post 都执行相同的操作,除了这个注释:

注意:虽然对 defer 的要求与 post 相同,但 post 的使用传达了一种偏好,即调用者不会阻塞 f1 进程的第一步,而 defer 传达一种偏好,即调用者确实阻塞了 f1 的第一步。 f1。 defer 的一种用途是传达调用者的意图,即 f1 是当前调用上下文的延续。执行器可以使用此信息来优化或以其他方式调整调用 f1 的方式。 ——尾注

https://www.boost.org/doc/libs/1_67_0/doc/html/boost_asio/reference/Executor1.html

因此,链接延续的责任似乎已成为Executor 模型的实现细节。

据我所知,这意味着您需要做的就是调用免费函数 defer(executor, handler) 并且执行器将“做正确的事”

更新:

找到了一些说明如何通过最终执行程序链接处理程序的文档:

文档来源:https://github.com/chriskohlhoff/asio-tr2/blob/master/doc/executors.qbk

示例:https://github.com/chriskohlhoff/executors/blob/v0.2-branch/src/examples/executor/async_op_2.cpp

请参阅 async_op_2.cpp 中的第 38 行以上

【讨论】:

  • 但我不能调用defer(executor, handler),因为async_read_until 负责调用处理程序。你能举个例子吗?
  • @herambled 我不能。 asio 文档中似乎没有足够的信息。
  • 那个简约的文档...无论如何,感谢您为写答案所做的努力。我想我找到了一种 解决方法。
  • @herambled 过去我曾通过电子邮件向 Christopher Kohlhoff 寻求帮助。他已经两次慷慨地回应,没有提到钱。他可能会很乐意解释 - 特别是如果您愿意为他更新文档;)
  • @herambled 这就是这里描述的方法:open-std.org/jtc1/sc22/wg21/docs/papers/2014/…
【解决方案2】:

玩了一会,发现asio_handler_is_continuation 并没有被弃用;目前没有办法用defer代替。

为了将任何post 调用重定向到defer,我提供了以下自定义执行器:

template<typename UnderlyingExecutor, typename std::enable_if<boost::asio::is_executor<UnderlyingExecutor>::value, int>::type = 0>
class continuation_executor
{
    private:
        UnderlyingExecutor _ex;

    public:

        continuation_executor(UnderlyingExecutor ex)
            :_ex(ex){}

        template<class Function, class Allocator>
        void post(Function f, Allocator a)
        {
            std::cout<<"Redirected to defer()"<<std::endl;
            _ex.defer(BOOST_ASIO_MOVE_CAST(Function)(f),a);
        }

        template<class Function, class Allocator>
        void defer(Function f, Allocator a)
        {
            std::cout<<"defer() called"<<std::endl;
            _ex.defer(BOOST_ASIO_MOVE_CAST(Function)(f),a);
        }

        template<class Function, class Allocator>
        void dispatch(Function f, Allocator a)
        {
            std::cout<<"dispatch() called"<<std::endl;
            _ex.dispatch(BOOST_ASIO_MOVE_CAST(Function)(f),a);
        }

        auto context() -> decltype(_ex.context())
        {
            return _ex.context(); 
        }

        void on_work_started()
        {
            _ex.on_work_started();
        }
        void on_work_finished()
        {
            _ex.on_work_finished();
        }
};

它确实是一个微不足道的执行器,完全依赖于底层执行器,continuation_executor::post 重定向到底层执行器的defer

但是当我使用类似bind_executor(conti_exec, handler) 的东西将处理程序传递给async_read_some 时,我得到以下输出:

dispatch() called

所以传递的处理程序不会通过post() 被调度;它是通过其他方式安排的。具体来说,像asio::async_read_some这样的内置异步函数通过scheduler::post_immediate_completion调度内部操作对象,然后io_context::run执行操作。

异步操作完成后,调用操作对象的complete方法来执行用户提供的处理程序。至少在当前实现中,complete 方法使用关联的执行程序的 dispatch 方法来运行处理程序。上面的钩子没有地方。所以它完全过时了;尝试使用 defer 而不是 asio_handler_is_continuation 是不走运的。

我在我的问题中所说的,“从 Boost 1.66 开始,上面的代码不太可能有任何效果(没有 BOOST_ASIO_NO_DEPRECATION 宏)。”,是完全错误的。 asio_handler_is_continuation 仍然有效,它是not deprecated as of 1.67

这是asio_handler_is_continuation仍然有效的证据:

  // Start an asynchronous send. The data being sent must be valid for the
  // lifetime of the asynchronous operation.
  template <typename ConstBufferSequence, typename Handler>
  void async_send(base_implementation_type& impl,
      const ConstBufferSequence& buffers,
      socket_base::message_flags flags, Handler& handler)
  {
    bool is_continuation =
      boost_asio_handler_cont_helpers::is_continuation(handler);

    // Allocate and construct an operation to wrap the handler.
    typedef reactive_socket_send_op<ConstBufferSequence, Handler> op;
    typename op::ptr p = { boost::asio::detail::addressof(handler),
      op::ptr::allocate(handler), 0 };
    p.p = new (p.v) op(impl.socket_, impl.state_, buffers, flags, handler);

    BOOST_ASIO_HANDLER_CREATION((reactor_.context(), *p.p, "socket",
          &impl, impl.socket_, "async_send"));

    start_op(impl, reactor::write_op, p.p, is_continuation, true,
        ((impl.state_ & socket_ops::stream_oriented)
          && buffer_sequence_adapter<boost::asio::const_buffer,
            ConstBufferSequence>::all_empty(buffers)));
    p.v = p.p = 0;
  }

请注意,它使用boost_asio_handler_cont_helpers 来确定处理程序是否继续。 boost_asio_handler_cont_helpers 内部调用 asio_handler_is_continuation

async_sendasync_write_some 在内部使用。我没有检查 asio 库提供的每个内置异步任务,但我很确定其他异步任务以相同的方式执行它的处理程序。

因此,如果您希望内置异步任务继续执行您的处理程序,您将不得不依赖asio_handler_is_continuationdefer 不会完全替换它! defer 只能在您直接从代码安排处理程序时使用。

【讨论】:

  • 理论上这听起来不错,但 Networking.TS 没有 asio_handler_is_continuation 的等价物。该功能会消失吗?
  • @VinnieFalco 我想是的。如果不控制内部操作如何提交给io_context,我们想要asio_handler_is_continuation的效果是不可能的。快速浏览一下,Networking.TS 并没有对如何将工作提交给io_context 提供具体限制。因此,除非他们提供像 net_handler_is_continuation 这样的指标,否则该功能肯定会消失:/
【解决方案3】:

看来,https://github.com/chriskohlhoff/asio-tr2/blob/master/doc/executors.qbk 上的代码中的 cmets 实际上包含关于 dispatch()post()defer() 的最详细描述。

defer() 将新创建的作业的处理推迟到当前作业完成之后。它确实将其推迟到其他排队的作业之后。在当前作业完成后推迟有很大的优势,新作业可以在与当前作业相同的线程中运行。默认执行程序将尝试这样做。并且由于新作业很可能会使用当前作业中的一些或更多数据,因此保持在同一个线程和同一个 CPU 内核中会大大提高缓存局部性,从而减少总执行时间并提高吞吐量。

换句话说:在您的完成处理程序/任务中,恰好启动一个新的完成处理程序/任务,您几乎总是希望使用defer() 而不是post()。如果新任务在当前任务结束时启动,则尤其如此。

但是,那些启动多个新任务的任务应仅通过defer() 提交最相关的任务(通常是最后一个任务),并为所有其他任务使用post()

仅对于那些非常简单的任务,考虑通过dispatch() 而不是post()queue() 提交它们:如果规则允许(例如,它们被分派到的链,当前是空的queue),然后它们将直接运行,避免所有排队和取消排队的延迟。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-11
    • 1970-01-01
    • 1970-01-01
    • 2019-02-21
    • 2018-11-26
    • 2015-08-08
    • 2016-11-15
    • 1970-01-01
    相关资源
    最近更新 更多