На Boost 1.66, у Asio есть осуждается asio_handler_is_continuation
функция крюка, способствующая использованию defer
функция. Кажется, что defer
функция ведет себя точно так же, как post
когда asio_handler_is_continuation == true. Тем не менее, способ использования 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
завершено, переданный лямбда-обработчик будет вызван с использованием некоторых средств, эквивалентных boost::asio::post
, Но async_write
внутри лямбда-обработчика есть продолжение последней асинхронной задачи, поэтому я хочу вызвать лямбда-обработчик, используя defer
воспользоваться преимуществом оптимизации.
Есть ли способ использовать defer
(вместо post
) вызвать лямбда-обработчик в приведенном выше примере?
ОРИГИНАЛЬНАЯ ПОЧТА: Я пытаюсь написать простую инициирующую функцию async_echo
похож на тот, что в зверь документ, кроме той части, которая вызывает boost::asio::async_write
будет называться продолжением. Чтобы достичь этого, предварительная промежуточная операция boost::asio::async_read_until
должен вызвать обработчик *this
как продолжение.
Это та часть, на которую я ссылаюсь в примере async_echo документа beast:
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 :: post ().», *this
не будет вызываться с помощью defer
то есть как продолжение.
Есть ли обходной путь, который делает boost::asio::async_read_until
вызвать обработчик, используя defer
? И есть ли хорошие примеры, которые используют defer
функционировать?
Это озадачило меня и в прошлом.
Executor::defer
а также Executor::post
оба выполняют одну и ту же операцию, за исключением этой заметки:
Примечание. Хотя требования, предъявляемые к отложенному переносу, идентичны требованиям 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
модель.
Что, насколько я могу судить, означает, что все, что вам нужно сделать, это вызвать бесплатную функцию 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
,
Но когда я передаю обработчик async_read_some
с чем-то вроде bind_executor(conti_exec, handler)
Я получаю следующий вывод:
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_send
используется async_write_some
внутренне. Я не проверял все встроенные асинхронные задачи, которые предоставляет библиотека asio, но я вполне уверен, что другие асинхронные задачи выполняют свой обработчик таким же образом.
Итак, если вы хотите, чтобы встроенные асинхронные задачи выполняли ваш обработчик как продолжение, вам придется положиться на asio_handler_is_continuation
, defer
не собирается полностью замени это! defer
может использоваться только тогда, когда вы планируете свой обработчик непосредственно из вашего кода.