asio C++ library

PrevUpHomeNext

Proposed Standard Executors

Asio provides a complete implementation of the proposed standard executors, as described in P0443r13, P1348r0, and P1393r0.

Just as with executors under the Networking TS model, a standard executor represents a policy as to how, when, and where a piece of code should be executed. Most existing code should continue to work with little or no change.

Standard Executor Implementations in Asio

The io_context::executor_type, thread_pool::executor_type, system_executor, and strand executors meet the requirements for the proposed standard executors. For compatibility, these classes also meet the requirements for the Networking TS model of executors.

Standard Executor Use in Asio

All I/O objects such as ip::tcp::socket, asynchronous operations, and utilities including dispatch, post, defer, get_associated_executor, bind_executor, make_work_guard, spawn, co_spawn, async_compose, use_future, etc., can interoperate with both proposed standard executors, and with Networking TS executors. Asio's implementation determines at compile time which model a particular executor meets; the proposed standard executor model is used in preference if both are detected.

Support for the existing Networking TS model of executors can be disabled by defining ASIO_NO_TS_EXECUTORS.

Polymorphic I/O Executor

The any_io_executor type alias is the default runtime-polymorphic executor for all I/O objects. This type alias points to the execution::any_executor<> template with a set of supportable properties specified for use with I/O.

This new name may break existing code that directly uses the old polymorphic wrapper, executor. If required for backward compatibility, ASIO_USE_TS_EXECUTOR_AS_DEFAULT can be defined, which changes the any_io_executor type alias to instead point to the executor polymorphic wrapper.

Implementing a Minimal I/O Executor

Standard executor properties make what were previously hard requirements on an executor (such as work counting, or the ability to distinguish between post, dispatch, and defer) into optional facilities. With this relaxation, the minimal requirements for an I/O executor are:

The following example shows a minimal I/O executor. Given a queue submission operation implemented elsewhere:

queue_t queue_create();
template <typename F> void queue_submit(queue_t q, F f);

the executor may be defined as follows:

struct minimal_io_executor
{
  asio::execution_context* context_;
  queue_t queue_;

  bool operator==(const minimal_io_executor& other) const noexcept
  {
    return context_ == other.context_ && queue_ == other.queue_;
  }

  bool operator!=(const minimal_io_executor& other) const noexcept
  {
    return !(*this == other);
  }

  asio::execution_context& query(
      asio::execution::context_t) const noexcept
  {
    return *context_;
  }

  static constexpr asio::execution::blocking_t::never_t query(
      asio::execution::blocking_t) noexcept
  {
    // This executor always has blocking.never semantics.
    return asio::execution::blocking.never;
  }

  template <class F>
  void execute(F f) const
  {
    queue_submit(queue_, std::move(f));
  }
};

This executor may be created as follows:

asio::execution_context context;
queue_t queue = queue_create();
minimal_io_executor executor{&context, queue};

and then used with I/O objects:

asio::ip::tcp::acceptor acceptor(executor);

or assigned into the any_io_executor polymorphic wrapper:

asio::any_io_executor poly_executor = executor;

Traits for Deducing Conformance to the Executor Concept

Older C++ standards and compilers require some assistance to determine whether an executor implementation conforms to the executor concept and type requirements. This is achieved through specialisation of traits. The following code shows a specialisation of these traits for the minimal_io_executor example from above:

namespace asio {
namespace traits {

#if !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT)

template <typename F>
struct execute_member<minimal_io_executor, F>
{
  static constexpr bool is_valid = true;
  static constexpr bool is_noexcept = true;
  typedef void result_type;
};

#endif // !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT)
#if !defined(ASIO_HAS_DEDUCED_EQUALITY_COMPARABLE_TRAIT)

template <>
struct equality_comparable<minimal_io_executor>
{
  static constexpr bool is_valid = true;
  static constexpr bool is_noexcept = true;
};

#endif // !defined(ASIO_HAS_DEDUCED_EQUALITY_COMPARABLE_TRAIT)
#if !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT)

template <>
struct query_member<minimal_io_executor,
    asio::execution::context_t>
{
  static constexpr bool is_valid = true;
  static constexpr bool is_noexcept = true;
  typedef asio::execution_context& result_type;
};

#endif // !defined(ASIO_HAS_DEDUCED_QUERY_MEMBER_TRAIT)
#if !defined(ASIO_HAS_DEDUCED_QUERY_STATIC_CONSTEXPR_MEMBER_TRAIT)

template <typename Property>
struct query_static_constexpr_member<minimal_io_executor, Property,
    typename enable_if<
      std::is_convertible<Property, asio::execution::blocking_t>::value
    >::type>
{
  static constexpr bool is_valid = true;
  static constexpr bool is_noexcept = true;
  typedef asio::execution::blocking_t::never_t result_type;
  static constexpr result_type value() noexcept { return result_type(); }
};

#endif // !defined(ASIO_HAS_DEDUCED_QUERY_STATIC_CONSTEXPR_MEMBER_TRAIT)

} // namespace traits
} // namespace asio

Asio uses an extensive set of traits to implement all of the proposed standard executor functionality on older C++ standards. These traits may be found under the asio/traits include directory.


PrevUpHomeNext