如何让ASIO中创建的awaitable异步任务立即启动而非co_await时执行
问题:让Asio协程任务在创建后立即启动
我使用单线程io_context作为事件循环,存在多个不能阻塞主事件循环且需大量并行运行的重型任务。主循环中所有任务均为asio::awaitables<void>,可创建新的awaitable任务或通过asio::co_spawn提交到执行器。
目前将并发任务提交到thread_pool时,任务并非在awaitable创建时启动,而是要等到执行co_await时才会启动。这在需要提前生成并提交更多并发任务以便后续按需等待的场景下尤为麻烦。
如何让这些任务在awaitable创建完成后立即启动?以下是复现该问题的极简示例代码:
#define ASIO_STANDALONE 1 #define ASIO_HAS_CO_AWAIT 1 #define ASIO_HAS_STD_COROUTINE 1 // #include <iostream> #include <thread> #include <asio/awaitable.hpp> #include <asio/bind_cancellation_slot.hpp> #include <asio/cancellation_signal.hpp> #include <asio/co_spawn.hpp> #include <asio/io_service.hpp> #include <asio/post.hpp> #include <asio/steady_timer.hpp> #include <asio/thread_pool.hpp> using namespace std; auto my_async(auto& context, auto&& func, auto&& handler) { return async_initiate<decltype(handler), void(std::error_code e)>( [&context](auto&& handler, auto&& func) { asio::post(context, [func = std::forward<decltype(func)>(func)]() { func(); }); }, handler, std::forward<decltype(func)>(func)); } auto main() -> int { std::cout << "start program\n"; asio::thread_pool pool(4); asio::io_service io_service{}; std::thread jthrd{}; asio::executor_work_guard<asio::io_context::executor_type> guard{io_service.get_executor()}; jthrd = std::thread{[&io_service]() { std::cout << "start loop\n"; io_service.run(); }}; asio::steady_timer timer{asio::system_executor{}, asio::steady_timer::duration::max()}; asio::cancellation_signal signal; auto cancel = signal.slot(); auto expansive_task_generator = [&](std::string coroname) { return [&, coroname = std::move(coroname)] { std::cout << "begin : " << coroname << " with tid: " << std::this_thread::get_id() << std::endl; auto timer = asio::steady_timer(io_service); timer.expires_from_now(std::chrono::seconds(10)); timer.wait(); std::cout << "10s timer expired" << std::endl; std::cout << "end : " << coroname << " with tid: " << std::this_thread::get_id() << std::endl; }; }; auto mainloop_awaitable = [&]() -> asio::awaitable<void> { std::cout << "run mainloop event\n"; auto awaitable1 = my_async(pool, expansive_task_generator("aw1"), asio::use_awaitable); auto awaitable2 = my_async(pool, expansive_task_generator("aw2"), asio::use_awaitable); // < possible medium expansive work here> auto awaitable3 = my_async(pool, expansive_task_generator("aw3"), asio::use_awaitable); auto awaitable4 = my_async( pool, [&timer]() { timer.expires_at(asio::steady_timer::time_point::min()); }, asio::use_awaitable); auto timer = asio::steady_timer(io_service); timer.expires_from_now(std::chrono::seconds(10)); co_await timer.async_wait(asio::use_awaitable); std::cout << "before co_await\n"; // My expectation: tasks are running already co_await std::move(awaitable1); // Problem: These are all executed serially // < possible medium expansive work here> co_await std::move(awaitable2); // Problem: These are all executed serially co_await std::move(awaitable3); // Problem: These are all executed serially // < possible medium expansive work here> co_await std::move(awaitable4); // Problem: These are all executed serially co_await timer.async_wait(asio::use_awaitable); }; auto mainloop_completion = [](std::exception_ptr e) { if (e) { try { std::rethrow_exception(e); } catch (const std::exception& e) { std::cerr << "mainloop failed with: " << e.what() << std::endl; } catch (...) { std::cerr << "mainloop failed with unknown exception" << std::endl; } } }; asio::co_spawn(io_service.get_executor(), std::move(mainloop_awaitable), asio::bind_cancellation_slot(cancel, mainloop_completion)); guard.reset(); jthrd.join(); return 0; }
核心原因
你的my_async实现中,任务提交逻辑被放在async_initiate的lambda内部,而这个lambda只会在首次执行co_await时才会触发——这是Asiouse_awaitable的默认行为:awaitable对象本质是一个"等待器",实际异步操作要等到co_await触发时才会启动。
解决方案
方案一:修改my_async,创建时立即提交任务
将任务提交逻辑从async_initiate中抽离,在创建awaitable对象的同时就执行任务提交,确保任务立即启动:
auto my_async(auto& context, auto&& func) -> asio::awaitable<void> { // 将任务包装为awaitable并立即提交到线程池,任务启动后返回等待句柄 auto task = [func = std::forward<decltype(func)>(func)]() -> asio::awaitable<void> { func(); co_return; }; return asio::co_spawn(context, std::move(task), asio::use_awaitable); }
使用时直接调用,无需传入use_awaitable:
auto awaitable1 = my_async(pool, expansive_task_generator("aw1"));
方案二:直接用co_spawn启动任务(推荐)
这是你提到的类似asio::future::async_wait(asio::use_awaitable)的实现方式,co_spawn在调用时就会将任务提交到指定执行器,任务立即启动,返回的awaitable仅用于后续等待任务完成:
修改后的mainloop_awaitable示例:
auto mainloop_awaitable = [&]() -> asio::awaitable<void> { std::cout << "run mainloop event\n"; // 调用co_spawn时任务立即启动,保存awaitable用于后续等待 auto awaitable1 = asio::co_spawn(pool, expansive_task_generator("aw1"), asio::use_awaitable); auto awaitable2 = asio::co_spawn(pool, expansive_task_generator("aw2"), asio::use_awaitable); // < 此处可执行中等开销任务 > auto awaitable3 = asio::co_spawn(pool, expansive_task_generator("aw3"), asio::use_awaitable); auto awaitable4 = asio::co_spawn(pool, [&timer]() { timer.expires_at(asio::steady_timer::time_point::min()); }, asio::use_awaitable); auto timer = asio::steady_timer(io_service); timer.expires_from_now(std::chrono::seconds(10)); co_await timer.async_wait(asio::use_awaitable); std::cout << "before co_await\n"; // 此时aw1/aw2/aw3/aw4已在并行运行 co_await std::move(awaitable1); // < 中等开销任务 > co_await std::move(awaitable2); co_await std::move(awaitable3); // < 中等开销任务 > co_await std::move(awaitable4); co_await timer.async_wait(asio::use_awaitable); };
关键说明
asio::co_spawn调用时即完成任务提交,任务会在thread_pool的空闲线程上立即启动,无需等待co_await- 保存
co_spawn返回的awaitable对象,后续通过co_await仅等待任务完成,不影响任务的并行执行 - 该方案完全符合Asio协程设计模式,无需手动实现复杂的异步初始化逻辑
内容的提问来源于stack exchange,提问作者Fabian Keßler
相关产品推荐
相关产品推荐

