boost-asio

How can co_spawn be used with co_await?


I am currently creating multiple task_sub() at the beginning of the task_main() function, and waiting for these task_sub() to end before exiting task_main(). There is only one thread. What should I do?

Is there any way to achieve this requirement? Note: task_main() and task_sub()are not at the same level, and task_sub()must be created in task_main().

asio 1.31.0


awaitable<void> task_sub()
{
    // TODO something
}

awaitable<void> task_main()
{
    printf("task_main begin\n");
    
    // create some task_sub and execute
    auto task1 = co_spawn(co_await this_coro::executor, task_sub(), xxxxxx?);
    auto task2 = co_spawn(co_await this_coro::executor, task_sub(), xxxxxx?);

    // main loop
    // TODO something

    // waiting task
    co_await task1;
    co_await task2;

    printf("task_main end\n");
}

I have tried using use_future to wait for the task to end, but it does not support co_await and task1.wait() is sync and blocked, which will block the main loop.

awaitable<void> task_main()
{
    printf("task_main begin\n");

    // create some task_sub and execute
    //
    std::future<void> task1 = co_spawn(co_await this_coro::executor, task_sub(), use_future);
    std::future<void> task2 = co_spawn(co_await this_coro::executor, task_sub(), use_future);

    // main loop
    // TODO something

    // waiting task
    //co_await task1;
    //co_await task2;

    //is blocked
    task1.wait();
    task2.wait();

    printf("task_main end\n");
}

I have also tried using channel, it can work, but it's not beautiful


//...
#include <asio/experimental/channel.hpp>

using exit_channel = asio::experimental::channel<void(asio::error_code)>;

awaitable<void> task_sub(exit_channel& channel)
{
    // TODO something

    //end
    co_await channel.async_send(error_code{});
}

awaitable<void> task_main()
{
    printf("task_main begin\n");

    auto ctx = co_await this_coro::executor;

    // create some task_sub and execute
    exit_channel task1_channel(ctx, 1);
    exit_channel task2_channel(ctx, 1);
    co_spawn(ctx, task_sub(task1_channel), detached);
    co_spawn(ctx, task_sub(task2_channel), detached);

    // main loop
    // TODO something

    // waiting task
    co_await task1_channel.async_receive();
    co_await task2_channel.async_receive();

    printf("task_main end\n");
}

And co_spawn(,,use_awaitable), it won't start executing at co_spawn, so it's not what you want.


Solution

  • Like I answered previously on the question you deleted, you can use use_promise:

    Live On Coliru¹

    #include <asio.hpp>
    #include <asio/experimental/promise.hpp>
    #include <asio/experimental/use_promise.hpp>
    #include <stdio.h>
    using asio::awaitable;
    using asio::experimental::use_promise;
    using namespace std::chrono_literals;
    namespace this_coro = asio::this_coro;
    
    awaitable<void> delay(auto dur) {
        co_await asio::steady_timer{co_await this_coro::executor, dur}.async_wait();
    }
    
    awaitable<void> task_sub(char const* caption, int n, auto ival) {
        for (int i = 0; i < n; ++i) {
            printf("task_sub '%s' %d\n", caption, i);
            co_await delay(ival);
        }
    }
    
    awaitable<void> task_main()
    {
        printf("task_main begin\n");
        
        auto ex = co_await this_coro::executor;
        // create some task_sub and execute
        auto task1 = co_spawn(ex, task_sub("task1", 5, 500ms), use_promise);
        auto task2 = co_spawn(ex, task_sub("task2", 4, 750ms), use_promise);
    
        // main loop
        // TODO something
        co_await task_sub("task_main", 3, 1000ms);
    
        printf("task_main wait\n");
    
        co_await std::move(task1);
        co_await std::move(task2);
    
        printf("task_main end\n");
    }
    
    int main() {
        asio::io_context ctx;
        co_spawn(ctx, task_main(), asio::detached);
        ctx.run();
    }
    

    See it live:

    ¹ using Boost Asio online, because it doesn't have standalone Asio

    Parallel Groups

    I'd still suggest parallel groups with awaitable operators for syntactic sugar:

    Live On Coliru

    #include <boost/asio.hpp>
    #include <boost/asio/experimental/promise.hpp>
    #include <boost/asio/experimental/use_promise.hpp>
    #include <boost/asio/experimental/awaitable_operators.hpp>
    #include <stdio.h>
    namespace asio = boost::asio;
    using asio::awaitable;
    using asio::experimental::use_promise;
    using namespace std::chrono_literals;
    using namespace asio::experimental::awaitable_operators;
    namespace this_coro = asio::this_coro;
    
    awaitable<void> delay(auto dur) {
        co_await asio::steady_timer{co_await this_coro::executor, dur}.async_wait();
    }
    
    awaitable<void> task_sub(char const* caption, int n, auto ival) {
        for (int i = 0; i < n; ++i) {
            printf("task_sub '%s' %d\n", caption, i);
            co_await delay(ival);
        }
        printf("task_sub '%s' done\n", caption);
    }
    
    awaitable<void> task_main() {
        printf("task_main begin\n");
        
        auto ex = co_await this_coro::executor;
        co_await (                              //
            task_sub("task1", 5, 500ms)         //
            && task_sub("task2", 4, 750ms)      //
            && task_sub("task_main", 3, 1000ms) //
        );
    
        printf("task_main end\n");
    }
    
    int main() {
        asio::io_context ctx;
        co_spawn(ctx, task_main(), asio::detached);
        ctx.run();
    }
    

    Printing

    task_main begin
    task_sub 'task1' 0
    task_sub 'task2' 0
    task_sub 'task_main' 0
    task_sub 'task1' 1
    task_sub 'task2' 1
    task_sub 'task_main' 1
    task_sub 'task1' 2
    task_sub 'task1' 3
    task_sub 'task2' 2
    task_sub 'task_main' 2
    task_sub 'task1' 4
    task_sub 'task2' 3
    task_sub 'task1' done
    task_sub 'task_main' done
    task_sub 'task2' done
    task_main end