chriskohlhoff / asio

Asio C++ Library
http://think-async.com/Asio
4.85k stars 1.2k forks source link

Creating io_context with custom executor #1260

Open KuDeSnik33ra opened 1 year ago

KuDeSnik33ra commented 1 year ago

Is it possible - in latest versions of asio - to create io_context with custom executor? I was trying to do it in many way, like:

class MyExecutor {
public:
    template <typename Function>
    void execute(Function&& f) const {
        std::cout << "Executing function on MyExecutor\n";
        f();
    }
};

asio::io_context test{ MyExecutor{} };

error C2440: 'initializing': cannot convert from 'initializer list' to 'asio::io_context'

and this way:

struct minimal_io_executor
{
    asio::execution_context* context_;

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

    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
    {
    }
};

asio::io_context context;
minimal_io_executor executor{ &context };
asio::post(executor, [] {});

error C2672: 'asio::post': no matching overloaded function found

Can anyone, please, help me to understand - what I am doing wrong? And where is the documentation about that?

My overall task is to run io_context on windows(IOCP) on separate thread, but call the completition callbacks in the main MsgCycle thread - so in the iocp thread I have to push callback to a queue and call SetEvent() for MsgCycle's MsgWaitForMultipleObjects().

I need this because the app is using Lua scripts, which can be called only in one thread, so suggestions not to do it at all are not relevant.

KuDeSnik33ra commented 1 year ago

I've read the https://www.boost.org/doc/libs/develop/doc/html/boost_asio/std_executors.html After adding

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(BOOST_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(BOOST_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(BOOST_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(BOOST_ASIO_HAS_DEDUCED_QUERY_STATIC_CONSTEXPR_MEMBER_TRAIT)

    } // namespace traits
}

to the minimal_io_executor way compiled successfully

Now the question is: Is this proper non-deprecated way to create custom executor? Is there another way to do it?

And another question: Is it the same - to create socket passing io_context, and to create socket passing io_context->get_executor()?

KuDeSnik33ra commented 1 year ago

Added

minimal_io_executor(minimal_io_executor&& other) : context_(other.context_)
{
    other.hEvent = NULL;
}

error C2679: binary '=': no operator found which takes a right-hand operand of type 'minimal_io_executor' (or there is no acceptable conversion)

So, the executor can not own any data - only store references/pointers to data, created elsewhere.

KuDeSnik33ra commented 1 year ago

How can I store the 'f' argument to a queue? What type should I use for the queue?

template <class F>
void execute(F f) const
{
}
KuDeSnik33ra commented 1 year ago

Here is the soultion, which seems to be working (maybe somebody will need it too). Does anybody have any suggestions?

#include <functional>
#include <asio.hpp>

class AsioFuncWrapperBase
{
public:
    virtual void execute() = 0;
    virtual ~AsioFuncWrapperBase() = default;
};

template <class F>
class AsioFuncWrapper : public AsioFuncWrapperBase
{
public:
    AsioFuncWrapper(F f) : m_f(ASIO_MOVE_CAST(F)(f)) {};

    virtual void execute() override { m_f(); };

private:
    F m_f;
};

using AsioFuncWrapperBasePtr = std::shared_ptr<AsioFuncWrapperBase>;
using AsioWinRemoteExecutorPushFuncType = std::function<void(const AsioFuncWrapperBasePtr& func)>;

struct AsioWinRemoteExecutor
{
    asio::execution_context* context_;
    AsioWinRemoteExecutorPushFuncType& pushFuncProc;

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

    bool operator!=(const AsioWinRemoteExecutor& 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
    {
        if (pushFuncProc)
        {
            pushFuncProc(AsioFuncWrapperBasePtr(new AsioFuncWrapper(ASIO_MOVE_CAST(F)(f))));
        }
    }
};

namespace asio {

namespace traits {

#if !defined(ASIO_HAS_DEDUCED_EXECUTE_MEMBER_TRAIT)

template <typename F>
struct execute_member<AsioWinRemoteExecutor, 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<AsioWinRemoteExecutor>
{
    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<AsioWinRemoteExecutor,
    execution::context_t>
{
    static constexpr bool is_valid = true;
    static constexpr bool is_noexcept = true;
    typedef 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<AsioWinRemoteExecutor, Property,
    typename enable_if<
    std::is_convertible<Property, execution::blocking_t>::value
>::type>
{
    static constexpr bool is_valid = true;
    static constexpr bool is_noexcept = true;
    typedef 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