You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

单线程下如何用co_spawn结合co_await实现task_main等待task_sub完成?

问题:Asio单线程下等待所有子协程完成的正确实现

需求说明

使用Asio 1.31.0,需实现以下逻辑:

  • 在task_main()函数开头创建多个task_sub()协程
  • 单线程环境下,task_main()必须等待所有task_sub()执行完毕后再退出
  • task_sub()必须在task_main()内部创建,二者并非同级协程

尝试过的方案及问题

1. 使用use_future

无法通过co_await直接等待std::future,调用wait()会同步阻塞主事件循环,导致线程卡住:

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

    // 创建并执行子协程
    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);

    // 主循环逻辑
    // TODO something

    // 等待子协程完成
    // co_await task1; // 无法直接co_await std::future
    // co_await task2;

    // 此处会同步阻塞主循环
    task1.wait();
    task2.wait();

    printf("task_main end\n");
}

2. 使用channel

可以实现需求,但需要为每个子协程单独创建通道,代码写法冗余且不够优雅:

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

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

awaitable<void> task_sub(exit_channel& channel)
{
    // 子协程业务逻辑
    // TODO something

    // 通知主协程自身完成
    co_await channel.async_send(error_code{});
}

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

    auto ctx = co_await this_coro::executor;

    // 创建子协程及对应通道
    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);

    // 主循环逻辑
    // TODO something

    // 等待所有子协程完成
    co_await task1_channel.async_receive();
    co_await task2_channel.async_receive();

    printf("task_main end\n");
}

3. 使用use_awaitable(错误用法)

直接co_spawn传入use_awaitable但未立即处理返回的awaitable时,子协程不会启动,不符合需求。


正确实现方案

方案1:容器存储awaitable批量等待

核心逻辑:

  1. 用co_spawn启动子协程时,将返回的awaitable<void>存入容器
  2. 在需要等待的位置,循环co_await所有子协程的awaitable对象

这种方式既保证子协程立即启动,又能非阻塞等待所有任务完成,代码简洁。

示例代码

#include <asio.hpp>
#include <vector>
#include <cstdio>

using namespace asio;

awaitable<void> task_sub()
{
    printf("task_sub start\n");
    // 模拟子协程业务逻辑(异步延迟)
    co_await steady_timer(co_await this_coro::executor, std::chrono::seconds(1)).async_wait(use_awaitable);
    printf("task_sub end\n");
}

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

    auto executor = co_await this_coro::executor;
    std::vector<awaitable<void>> sub_tasks;

    // 创建并启动多个子协程,存储返回的awaitable
    sub_tasks.emplace_back(co_spawn(executor, task_sub(), use_awaitable));
    sub_tasks.emplace_back(co_spawn(executor, task_sub(), use_awaitable));

    // 主循环业务逻辑
    printf("task_main running main loop\n");
    // TODO: 执行主循环的异步操作

    // 等待所有子协程完成
    for (auto& task : sub_tasks)
    {
        co_await std::move(task);
    }

    printf("task_main end\n");
}

int main()
{
    io_context ctx;
    co_spawn(ctx, task_main(), detached);
    ctx.run();
    return 0;
}

方案2:使用when_all并行等待

如果需要并行等待所有子协程(无需逐个等待),可以使用Asio的experimental::when_all工具(需包含<asio/experimental/when_all.hpp>),进一步简化代码:

示例代码

#include <asio.hpp>
#include <asio/experimental/when_all.hpp>
#include <cstdio>

using namespace asio;

awaitable<void> task_sub()
{
    printf("task_sub start\n");
    co_await steady_timer(co_await this_coro::executor, std::chrono::seconds(1)).async_wait(use_awaitable);
    printf("task_sub end\n");
}

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

    auto executor = co_await this_coro::executor;

    // 创建并启动子协程
    auto task1 = co_spawn(executor, task_sub(), use_awaitable);
    auto task2 = co_spawn(executor, task_sub(), use_awaitable);

    // 主循环业务逻辑
    printf("task_main running main loop\n");
    // TODO: 主循环异步操作

    // 并行等待所有子协程完成
    co_await asio::experimental::when_all(std::move(task1), std::move(task2));

    printf("task_main end\n");
}

int main()
{
    io_context ctx;
    co_spawn(ctx, task_main(), detached);
    ctx.run();
    return 0;
}

方案说明

  • 子协程立即启动:co_spawn搭配use_awaitable时,子协程会在调用co_spawn时立即执行,满足“在task_main开头创建并启动”的需求
  • 非阻塞等待:通过co_await等待awaitable对象,不会阻塞主事件循环,Asio会自动调度其他就绪任务
  • 代码简洁:无需额外同步对象,仅用容器或when_all即可实现批量等待

内容的提问来源于stack exchange,提问作者kaho J

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 09:05:13