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

Boost Asio串口读取定时器第二次调用挂起问题咨询

RS485通信中二次调用readUntil挂起的问题分析与修复

问题根源

  1. 回调抛异常导致任务残留:第一次调用时,定时器回调直接抛出异常,终止io.run()的执行,此时async_read_until的回调仍留在io_context的任务队列中。第二次调用io.reset()仅重置了上下文的停止状态,并未清除队列中的旧回调。这些旧回调引用的是第一次调用时已销毁的栈局部变量,触发未定义行为,导致程序挂起。
  2. 异步操作错误处理违规:在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 19:00:58