Boost Beast的io_context能否二次调用run()?连接IO执行异常
我用一个简单的io_context对象初始化ssl_stream(通过async_resolve、async_connect和async_handshake),在不同作用域里调用async_read和async_write做连接IO操作。所有异步调用都在boost::asio::spawn创建的协程中执行,初始化和IO操作分别在不同协程里。
因为两个阶段分离,单次run()没法覆盖全部流程:初始化完成后第一次run()终止,所以启动IO操作的协程后需要调用第二次run(),但发现第二次run()直接退出,没执行刚创建的协程。请问怎么解决?是不是只能把连接生命周期放在单个协程里,或者在独立线程中持续运行run()?
场景伪代码如下:
boost::asio::io_context io_context_; std::optional<boost::beast::tcp_stream> stream_; std::optional<boost::asio::ip::tcp::resolver> resolver_; //STAGE 1 boost::asio::spawn(io_context_, [&](boost::asio::yield_context yield) { results = resolver_->async_resolve(host_,port_ , yield); ssl_stream_->next_layer().async_connect(results, yield); ssl_stream_->async_handshake(ssl::stream_base::client, yield); } try { io_context_.run(); } catch (...) { } //STAGE 2 beast::flat_buffer buffer; http::response<http::dynamic_body> res; boost::asio::spawn(io_context_, [&](boost::asio::yield_context yield) { beast::get_lowest_layer(*ssl_stream_).expires_after(kOpTimeout); auto sent = http::async_write(*ssl_stream_, beast_request, yield); auto read = http::async_read(*ssl_stream_, buffer, res, yield); }); try { io_context_.run(); return res; } catch (...) { }
问题根源
第一次调用io_context_.run()完成后,io_context会进入停止状态,内部工作计数器归零。此时即便通过spawn提交了新的协程任务,直接再次调用run()也不会处理这些任务,因为io_context还处于停止状态。
具体解决办法
1. 重置io_context状态
在第一次run()结束后,调用io_context_.reset()清除停止状态,这样第二次run()就能正常处理新提交的协程任务。修改后的代码片段:
// STAGE 1执行完毕 try { io_context_.run(); io_context_.reset(); // 重置停止状态 } catch (...) { } // STAGE 2提交协程后再run try { io_context_.run(); return res; } catch (...) { }
2. 让io_context在独立线程持续运行(推荐)
把io_context_.run()放到单独线程中一直运行,后续不管是初始化还是IO操作的协程,只要提交到io_context就会被自动处理。这种方式更贴合Asio的异步设计思想,避免多次调用run()和重置的繁琐操作。示例代码:
// 启动线程持续运行io_context std::thread io_thread([&](){ io_context_.run(); }); // 用promise同步初始化完成状态 std::promise<void> init_done; // STAGE 1:提交初始化协程 boost::asio::spawn(io_context_, [&](boost::asio::yield_context yield) { results = resolver_->async_resolve(host_,port_ , yield); ssl_stream_->next_layer().async_connect(results, yield); ssl_stream_->async_handshake(ssl::stream_base::client, yield); init_done.set_value(); // 标记初始化完成 }); // 等待初始化完成 init_done.get_future().wait(); // STAGE 2:提交IO操作协程 boost::asio::spawn(io_context_, [&](boost::asio::yield_context yield) { beast::get_lowest_layer(*ssl_stream_).expires_after(kOpTimeout); auto sent = http::async_write(*ssl_stream_, beast_request, yield); auto read = http::async_read(*ssl_stream_, buffer, res, yield); }); // 等待IO操作完成后,停止io_context并回收线程 io_context_.stop(); io_thread.join();
3. 将初始化与IO操作合并到单个协程
把初始化和后续IO逻辑放到同一个协程里,这样单次io_context_.run()就能处理完整流程,彻底避免分阶段的问题。这种方式逻辑更连贯,适合简单业务场景:
boost::asio::spawn(io_context_, [&](boost::asio::yield_context yield) { // STAGE 1:初始化流程 results = resolver_->async_resolve(host_,port_ , yield); ssl_stream_->next_layer().async_connect(results, yield); ssl_stream_->async_handshake(ssl::stream_base::client, yield); // STAGE 2:IO操作流程 beast::get_lowest_layer(*ssl_stream_).expires_after(kOpTimeout); auto sent = http::async_write(*ssl_stream_, beast_request, yield); auto read = http::async_read(*ssl_stream_, buffer, res, yield); }); try { io_context_.run(); return res; } catch (...) { }
内容的提问来源于stack exchange,提问作者Zohar81

