Boost Asio串口读取定时器第二次调用挂起问题咨询
RS485通信中二次调用readUntil挂起的问题分析与修复
问题根源
- 回调抛异常导致任务残留:第一次调用时,定时器回调直接抛出异常,终止
io.run()的执行,此时async_read_until的回调仍留在io_context的任务队列中。第二次调用io.reset()仅重置了上下文的停止状态,并未清除队列中的旧回调。这些旧回调引用的是第一次调用时已销毁的栈局部变量,触发未定义行为,导致程序挂起。 - 异步操作错误处理违规:在Boost.Asio的回调中抛出异常是不安全的,会破坏
io_context的正常执行流程,导致异步操作清理不彻底。
修复方案
方案1:避免回调抛异常,统一在函数内处理错误
修改回调逻辑,用状态标志传递错误,确保所有回调都能执行完毕,避免任务残留:
#include <boost/asio.hpp> #include <stdexcept> #include <sstream> #include <vector> namespace exception { struct read_exception : std::runtime_error { enum class Code { TIME_OUT, PORT }; Code code; read_exception(Code c, const std::string& msg) : std::runtime_error(msg), code(c) {} }; } boost::asio::io_context io; boost::asio::serial_port port; std::size_t readUntil(std::vector<char>& buffer, char delim, std::chrono::microseconds timeout) { using boost::system::error_code; error_code read_result {}; size_t msglen = 0; bool timed_out = false; // 先清空io_context残留任务,再重置状态 while (io.run_one()); io.reset(); boost::asio::system_timer timer(io, timeout); // 异步读取回调:取消定时器,记录操作结果 boost::asio::async_read_until(port, boost::asio::dynamic_buffer(buffer), delim, [&](error_code ec, size_t n) { timer.cancel(); read_result = ec; msglen = n; } ); // 定时器回调:取消串口操作,标记超时状态 timer.async_wait([&](error_code ec) { if (!ec) { port.cancel(); timed_out = true; } }); io.run(); // 确保所有回调执行完毕,无异常终止 // 统一处理错误 if (timed_out) { throw exception::read_exception(exception::read_exception::Code::TIME_OUT, "读取超时"); } if (read_result) { std::stringstream sstream; sstream << read_result; throw exception::read_exception(exception::read_exception::Code::PORT, sstream.str()); } return msglen; } int main() { // 初始化串口(示例) try { port.open("/dev/ttyUSB0", boost::system::error_code()); port.set_option(boost::asio::serial_port_base::baud_rate(9600)); // 配置其他串口参数(数据位、停止位、奇偶校验等) } catch (...) { // 处理串口打开失败 } for(size_t i = 0; i<10; i++) { try { std::vector<char> buffer; readUntil(buffer, '\'', std::chrono::microseconds(1000)); } catch (const exception::read_exception& e) { // 捕获并处理超时或串口错误 } } port.close(); return 0; }
方案2:使用局部io_context彻底隔离任务
每次调用readUntil时创建独立的io_context,避免全局上下文的任务残留问题:
#include <boost/asio.hpp> #include <stdexcept> #include <sstream> #include <vector> namespace exception { struct read_exception : std::runtime_error { enum class Code { TIME_OUT, PORT }; Code code; read_exception(Code c, const std::string& msg) : std::runtime_error(msg), code(c) {} }; } boost::asio::serial_port port; // 串口保持全局 std::size_t readUntil(std::vector<char>& buffer, char delim, std::chrono::microseconds timeout) { using boost::system::error_code; error_code read_result {}; size_t msglen = 0; bool timed_out = false; boost::asio::io_context io; // 局部io_context,每次调用都是全新环境 boost::asio::system_timer timer(io, timeout); boost::asio::async_read_until(port, boost::asio::dynamic_buffer(buffer), delim, [&](error_code ec, size_t n) { timer.cancel(); read_result = ec; msglen = n; } ); timer.async_wait([&](error_code ec) { if (!ec) { port.cancel(); timed_out = true; } }); io.run(); if (timed_out) { throw exception::read_exception(exception::read_exception::Code::TIME_OUT, "读取超时"); } if (read_result) { std::stringstream sstream; sstream << read_result; throw exception::read_exception(exception::read_exception::Code::PORT, sstream.str()); } return msglen; } // main函数同方案1
关键修复说明
- 移除回调内异常抛出:改用布尔标志
timed_out记录超时状态,在io.run()完成后统一抛出异常,确保所有回调都能执行完毕,避免任务队列残留。 - 清理全局io_context(方案1):通过
while(io.run_one())在重置前执行完所有残留任务,确保上下文干净。 - 局部io_context隔离(方案2):彻底避免全局上下文的任务交叉问题,每次调用都是独立的异步执行环境。
内容的提问来源于stack exchange,提问作者Typhaon
相关产品推荐
相关产品推荐

