как использовать boost :: asio :: defer () в составлении функции? - PullRequest
0 голосов
/ 04 мая 2018

В Boost 1.66 у Asio устарела функция asio_handler_is_continuation hook, способствующая использованию функции 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) для вызова лямбда-обработчика в приведенном выше примере?

ORIGINAL POST : я пытаюсь написать простую инициирующую функцию async_echo, аналогичную той, что есть в beast document , за исключением того, что будет вызвана часть, которая вызывает 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?

Ответы [ 2 ]

0 голосов
/ 05 мая 2018

Немного поиграв, получается, что 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 можно использовать только тогда, когда вы планируете обработчик непосредственно из вашего кода.

0 голосов
/ 04 мая 2018

Это озадачило меня и в прошлом.

Executor::defer и Executor::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

...