如何在组合函数中使用 boost::asio::d efer()



在 Boost 1.66 上,Asio 弃用了asio_handler_is_continuationhook 函数,促进了defer函数的使用。似乎defer函数的行为与postasio_handler_is_continuation==true 时完全相同。但是使用defer的方式与使用asio_handler_is_continuation的方式不同,我不确定如何正确使用defer

编辑:我认为下面的示例太冗长了,无法清楚地表达我的意思。下面是一个较短的示例:

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

现在,当async_read_until完成时,将使用一些等效于boost::asio::post的方法调用传递的 lambda 处理程序。但是 lambda 处理程序内部async_write是最后一个异步任务的延续,因此我想使用defer调用 lambda 处理程序以冒险进行优化。

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

原帖:我正在尝试编写一个简单的初始化函数async_echo类似于 beast 文档中的函数,只是调用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保证"处理程序的调用将以等效于使用 boost::asio::io_context::p ost().的方式执行",因此不会使用defer调用*this,即作为延续。

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

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

Executor::deferExecutor::post都执行相同的操作,但以下注释除外:

注意:尽管对 delay 的要求与 post 相同,但使用 post 传达了一个首选项,即调用方不会阻止 f1 进度的第一步,而 defer 传达的首选项是调用方确实阻止了 f1 的第一步。defer 的一个用途是传达调用方的意图,即 f1 是当前调用上下文的延续。执行程序可以使用此信息来优化或以其他方式调整调用 f1 的方式。——尾注

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

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

据我所知,这意味着您需要做的就是调用 free 函数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

请参阅第 38+ 行 async_op_2.cpp

玩了一会儿后,事实证明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仍然有效,并且从 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_continuation.defer不会完全取代它! 仅当您直接从代码调度处理程序时,才能使用defer

似乎在https://github.com/chriskohlhoff/asio-tr2/blob/master/doc/executors.qbk 上找到的代码中的注释实际上包含有关dispatch()post()defer()的最详细的描述。

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

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

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

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

最新更新