chriskohlhoff / asio

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

[feature request] make_parallel_group with use_promise support #1424

Open redboltz opened 5 months ago

redboltz commented 5 months ago

Motivation

I want to use async functions that have CompletionToken with std::variant. For example, the code example has two classes that tcp and tls. They have the same signature function async_write.

When I call async_func with deferred, that causes compile error due to return type mismatch. After some study, I use asio::experimental::use_promise. It works fine. See https://stackoverflow.com/a/77808212/1922763

        co_await std::visit(
            [&](auto& c) {
                return c.async_write(
                    // asio::deferred // caused visit return type mismatch
                    asio::experimental::use_promise
                );
            },
            con
        );

So far, so good. godbolt demo with #if 1 (individual waiting): https://godbolt.org/z/fWreK6hrn

Now, I want to combine this async function call with asio::experimental::make_parallel_group. However I got compile error.

godbolt demo with #if 0 (parallel waiting): https://godbolt.org/z/W3s76ra5z

It seems that asio::experimental::make_parallel_group supports only asio::deferred, IIUC. I guess that If asio::experimental::make_parallel_group supports asio::experimental::promise, then my problem would be solved. (combine std::visit with asio::experimental::use_promise and asio::experimental::make_parallel_group.

I understant that I use experimental features. I just wanted to notify the usecase that combines experimental features I desire.

All code

#include <boost/asio.hpp>
#include <boost/asio/experimental/parallel_group.hpp>
#include <boost/asio/experimental/promise.hpp>
#include <boost/asio/experimental/use_promise.hpp>
#include <vector>
#include <variant>
#include <iostream>

namespace asio = boost::asio;

struct tcp {
    template <typename CompletionToken>
    auto async_write(CompletionToken&& token) {
        return asio::async_initiate<
            CompletionToken, 
            void()
        >(
            [](auto completion_handler) {
                std::move(completion_handler)();
            },
            token
        );
    }
};

struct tls {
    template <typename CompletionToken>
    auto async_write(CompletionToken&& token) {
        return asio::async_initiate<
            CompletionToken, 
            void()
        >(
            [](auto completion_handler) {
                std::move(completion_handler)();
            },
            token
        );
    }
};

using connection = std::variant<tcp, tls>;

asio::awaitable<void> all() {
    auto exe = co_await asio::this_coro::executor;

    std::vector<connection> cons {tcp{}, tls{}};

#if 0
    // I can do individual waiting
    for (auto& con : cons) {
        co_await std::visit(
            [&](auto& c) {
                return c.async_write(
                    // asio::deferred // caused visit return type mismatch
                    asio::experimental::use_promise
                );
            },
            con
        );
    }
#else
    // I want to wait all async requests parallelly like this
    using op_type = asio::experimental::promise<
        void(), 
        asio::system_executor
    >;
    std::vector<op_type> ops; 
    for (auto& con : cons) {
        ops.push_back(
            std::visit(
                [&](auto& c) {
                    return c.async_write(
                        // asio::deferred // caused visit return type mismatch
                        asio::experimental::use_promise
                    );
                },
                con
            )
        );
    }
    co_await asio::experimental::make_parallel_group(ops).async_wait(
        asio::experimental::wait_for_all(),
        asio::deferred
        // asio::experimental::use_promise // also error
    );
#endif
}

int main() {
    std::cout << "start" << std::endl;
    asio::io_context ioc;
    asio::co_spawn(ioc.get_executor(), all(), asio::detached);
    ioc.run();
    std::cout << "finish" << std::endl;
}

Environment I tried