如何等待单个任务完成且不取消其他Boost Asio任务?
问题:在Boost.Asio中等待单个transfer任务完成且不取消其他任务
假设我运行多个transfer任务(数量为三个或更多),如何在bidirectional_transfer方法中等待单个任务完成,同时不取消仍在运行的其他任务?
当前代码实现如下:
#include <boost/asio.hpp> #include <boost/asio/ssl.hpp> #include <boost/asio/experimental/promise.hpp> #include <boost/asio/experimental/use_promise.hpp> #include <boost/asio/experimental/awaitable_operators.hpp> #include <iostream> #include <iomanip> namespace asio = boost::asio; namespace ssl = boost::asio::ssl; using tcp = asio::ip::tcp; asio::awaitable<void> transfer(ssl::stream<tcp::socket>& from, ssl::stream<tcp::socket>& to); asio::awaitable<void> bidirectional_transfer(ssl::stream<tcp::socket>& client_ssl, ssl::stream<tcp::socket>& server_ssl) { auto ex = co_await boost::asio::this_coro::executor; auto [order, ex0, ex1] = co_await asio::experimental::make_parallel_group( asio::co_spawn(ex, transfer(client_ssl, server_ssl), boost::asio::deferred), asio::co_spawn(ex, transfer(server_ssl, client_ssl), boost::asio::deferred) ).async_wait( asio::experimental::wait_for_one_success(), asio::deferred ); // 此时仍有一个任务在运行,需要在这里等待它完成 }
我需要类似wait_for_one_success的等待策略功能,但不会取消仍在运行的任务。当前实现中我使用了wait_for_one_error或operator &&。
编辑1:
任务无法比co_spawn存在更久,因为它按值接收awaitable:
template <typename Executor, typename AwaitableExecutor, BOOST_ASIO_COMPLETION_TOKEN_FOR( void(std::exception_ptr)) CompletionToken> inline BOOST_ASIO_INITFN_AUTO_RESULT_TYPE( CompletionToken, void(std::exception_ptr)) co_spawn(const Executor& ex, awaitable<void, AwaitableExecutor> a, CompletionToken&& token, constraint_t< (is_executor<Executor>::value || execution::is_executor<Executor>::value) && is_convertible<Executor, AwaitableExecutor>::value >)
解决方案
Boost.Asio的parallel_group默认在触发等待策略(比如wait_for_one_success)时会取消未完成的任务,要实现“等待第一个任务完成但不取消其他任务”的需求,需要避免使用parallel_group的内置取消机制,转而手动跟踪任务状态:
方法1:使用Promise跟踪第一个完成的任务,后续等待剩余任务
为每个transfer任务绑定完成回调,第一个任务完成时通知主协程,之后主协程再等待所有剩余任务完成:
asio::awaitable<void> bidirectional_transfer(ssl::stream<tcp::socket>& client_ssl, ssl::stream<tcp::socket>& server_ssl) { auto ex = co_await boost::asio::this_coro::executor; asio::experimental::promise<void> first_done_promise; auto first_done = first_done_promise.get_future(); // 存储所有任务的awaitable,用于后续等待 std::vector<asio::awaitable<void>> tasks; // 启动第一个transfer任务,完成时触发promise tasks.push_back([&]() -> asio::awaitable<void> { try { co_await transfer(client_ssl, server_ssl); first_done_promise.set_value(); } catch (...) { first_done_promise.set_exception(std::current_exception()); } }()); // 启动第二个transfer任务,仅在promise未触发时设置 tasks.push_back([&]() -> asio::awaitable<void> { try { co_await transfer(server_ssl, client_ssl); first_done_promise.set_value_noexcept(); } catch (...) { first_done_promise.set_exception_noexcept(std::current_exception()); } }()); // 可添加更多任务... // 等待第一个任务完成 co_await first_done; // 等待所有剩余任务完成,处理可能的异常 for (auto& task : tasks) { try { co_await std::move(task); } catch (const std::exception& e) { std::cerr << "剩余任务异常: " << e.what() << std::endl; } } }
方法2:使用asio::experimental::when_any(Boost 1.78+)
Boost 1.78及以上版本提供的when_any可以等待第一个awaitable完成,且不会取消其他任务,之后再等待剩余任务:
#include <boost/asio/experimental/when_any.hpp> asio::awaitable<void> bidirectional_transfer(ssl::stream<tcp::socket>& client_ssl, ssl::stream<tcp::socket>& server_ssl) { auto ex = co_await boost::asio::this_coro::executor; auto task1 = asio::co_spawn(ex, transfer(client_ssl, server_ssl), asio::deferred); auto task2 = asio::co_spawn(ex, transfer(server_ssl, client_ssl), asio::deferred); // 添加更多任务... // 等待第一个任务完成 auto [index, result] = co_await asio::experimental::when_any(task1, task2); // 等待剩余任务完成 if (index == 0) { try { co_await std::move(task2); } catch (...) {} // 若有更多任务,依次等待 } else { try { co_await std::move(task1); } catch (...) {} // 若有更多任务,依次等待 } }
关键注意事项
- 必须等待所有任务完成,否则
co_spawn持有的awaitable会提前销毁,导致未定义行为。 - 需处理任务抛出的异常,避免程序崩溃或资源泄漏。
内容的提问来源于stack exchange,提问作者Dmitriano
相关产品推荐
相关产品推荐

