如何检测处理程序是否为 ASIO strand wrap 并通过 strand 调用它?

How to detect if handler is an ASIO strand wrap and call it through the strand?

如果有一个通用方法采用一些处理程序:

template< typename HandlerType >
void Register( HandlerType && handler )
{
    m_handler( std::forward< HandlerType >( handler ) );
}

并且该处理程序将在将来的某个时候通过 io_service 调用:

void SomeEvent( )
{
    // compute someParameter

    m_IOService.post( std::bind( m_handler , someParameter ) );
}

如果 Register() 的调用者传递了由 strand 包裹的东西,如何检测到,如:

m_strand( m_IOService );

// ...

Register( m_strand.wrap( []( /*something*/ ){ /*...*/ } ) );

在这种情况下,应该如何更改 SomeEvent() 以便 post 处理程序通过 strand?

编辑

当我问这个问题时,我没有仔细阅读 io_service::strand::wrap docs,更具体地说:

(...) Given a function object with the signature:

R f(A1 a1, ... An an);

If this function object is passed to the wrap function like so:

strand.wrap(f);

then the return value is a function object with the signature

void g(A1 a1, ... An an);

that, when invoked, executes code equivalent to:

strand.dispatch(boost::bind(f, a1, ... an));

我确实需要的就是这个——我可以将 m_handler 声明为适当的 std::function<> 并通过 SomeEvent() 中的 io_service 简单地 post ].

看了@Arunmu 的回答我才意识到这一点,所以我接受了。尽管如此,@Richard Hodges 的回答对 ASIO 的执行程序逻辑以及它在独立版本中的改进方式有一些好处。

如果我理解清楚你的需求,那么如果像下面这样实现你就不需要做任何额外的事情(阅读代码中的注释以获得解释):

   #include <iostream>
    #include <type_traits>
    #include <thread>
    #include <memory>
    #include <asio.hpp>

    template <typename Handler>
    class GenHandler
    {
    public:
      GenHandler(Handler&& h): hndler_(std::forward<Handler>(h))
      {}

      template <typename... Args>
      void operator()(Args&&... args)
      {
        std::cout << "GenHandler called" << std::endl;
        hndler_();
      }
    private:
      Handler hndler_;
    };

    template<typename HandlerType>
    GenHandler<std::decay_t<HandlerType>> create_handler(HandlerType&& h)
    {
      return {std::forward<HandlerType>(h)};
    }

    template <typename Handler>
    void SomeEvent(asio::io_service& ios, Handler& h)
    {
      ios.post([=] ()mutable { h(); });
    }

    int main() {
      asio::io_service ios;
      asio::io_service::strand strand{ios};
      auto work = std::make_unique<asio::io_service::work>(ios);
      std::thread t([&]() { ios.run(); });

      // This creates a regular handler which when called by the 
      // io_context would first execute GenHandler::operator()
      // and inside of which it would call the lambda passed below.
      auto hndl = create_handler([] { 
                        std::cout << "Regular Handle" << std::endl; 
                   });
      SomeEvent(ios, hndl);

      ///-------- Example 2 ---------////

      // This creates a handler just like above, but instead wraps a
      // strand handler i.e when GenHandler::operator() gets called
      // it will execute the lambda passed to the wrap in the execution context
      // of the strand.
      auto hndl2 = create_handler(
                       strand.wrap([] { 
                          std::cout << "Strand handler-depth 2" << std::endl; 
                       }));

      // This is a regular strand wrap which is passed to the 
      // io_service execution context. The lambda passed in the strand::wrap
      // would be excuted the execution context of the strand.
      auto str_handler = strand.wrap([=]() mutable { 
                            std::cout <<"strand\n"; 
                            hndl2();  
                          });
      SomeEvent(ios, str_handler);
      work.reset();

      t.join();
      return 0;
    }

在第二个示例中,处理程序的调用顺序如下:

  1. io_service 传递给 strand::wrapped_handler。因此,wrapped_handler 持有的处理程序在链内执行。
  2. hndl2GenHandler 持有另一个 strand::wrapped_handler 也被称为链内。
  3. 当调用GenHandler::operator()时,它也会执行持有的strand::wrapped_handler。这是通过将 strand::wrapped_handler 持有的内部处理程序分派给链来完成的。

注意: 由于我不清楚的原因,strand::wrap 已被弃用。作者希望人们改用 bind_executor

对于 boost asio 我认为答案在这个模板函数中:

namespace boost_asio_handler_cont_helpers {

template <typename Context>
inline bool is_continuation(Context& context)
{
#if !defined(BOOST_ASIO_HAS_HANDLER_HOOKS)
  return false;
#else
  using boost::asio::asio_handler_is_continuation;
  return asio_handler_is_continuation(
      boost::asio::detail::addressof(context));
#endif
}

} // namespace boost_asio_handler_cont_helpers

如果我没有看错的话,它是用来检测是否有"context"(即strandio_service)在其中执行处理程序。

reactor 服务中的代码然后根据结果切换,是否在现有上下文中执行。

standalone asio 中有些变化。

现在有一个函数可以检测处理程序的上下文(如果有的话)。咨询了作者后写了这段代码

相关行是:

auto ex = asio::get_associated_executor(handler, this->get_io_service().get_executor());

和..

            asio::dispatch(ex, [handler = std::move(handler), future = std::move(future)]() mutable
                 {
// call the user-supplied handler
        });

这是来自 "long running task" 执行服务的生产代码:

    template<class Task, class Handler>
    void async_execute(implementation& impl, Task&& task, Handler&& handler)
    {
        VALUE_DEBUG_TRACE(module) << method(__func__, this);

        using task_type = std::decay_t<Task>;
        static_assert(is_callable_t<task_type, long_running_task_context>(), "");
        using result_type = std::result_of_t<task_type(long_running_task_context)>;
        using promise_type = std::promise<result_type>;
        using future_type = std::future<result_type>;
        using handler_type = std::decay_t<Handler>;
        static_assert(is_callable_t<handler_type, future_type>(), "");
        using handler_result_type = std::result_of<handler_type(future_type)>;

        auto ex = asio::get_associated_executor(handler, this->get_io_service().get_executor());

        if (not impl)
        {
            post(ex, [handler = std::forward<Handler>(handler)]() mutable
                 {
                     promise_type promise;
                     promise.set_exception(std::make_exception_ptr(system_error(errors::null_handle)));
                     handler(promise.get_future());
                 });
            return;
        }

        auto handler_work = make_work(ex);
        auto& ios = get_io_service();
        auto impl_ptr = impl.get();
        auto async_handler = [this,
                              &ios,
                              impl_ptr,
                              handler_work, ex,
                              handler = std::forward<Handler>(handler)]
        (detail::long_running_task_op::identifier ident,
         auto future) mutable
        {
            assert(impl_ptr);
            VALUE_DEBUG_TRACE(module) << method("async_execute::async_handler", this, ident);
            asio::dispatch(ex, [handler = std::move(handler), future = std::move(future)]() mutable
                 {
                     VALUE_DEBUG_TRACE(module) << method("async_execute::completion_handler");
                     handler(std::move(future));
                 });
            assert(impl_ptr);
            impl_ptr->remove_op(ident);
        };

        using async_handler_type = decltype(async_handler);
        static_assert(is_callable_t<async_handler_type, detail::long_running_task_op::identifier, future_type>(), "");




        auto op = detail::long_running_task_op(std::forward<Task>(task), std::move(async_handler));
        auto ident = op.get_identifier();
        impl->add_op(ident);

        auto lock = lock_type(this->_queue_mutex);
        _ops.emplace(ident, op);
        lock.unlock();

        this->post_execute();
    }