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.
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.
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
.
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.
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:
executor
concept.
execution::context
property, with the result
being execution_context&
or a reference to a class that
is derived from execution_context
.
execute
operation having,
at minimum, the execution::blocking.never
semantic.
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;
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.