基于 ROS Timer 延迟处理的消息逻辑优化
在 ROS 开发中,某个话题可能会高频发布消息,而业务逻辑希望“消息持续到来时不触发某个操作,消息停止一段时间后再触发”。这类需求本质上是 debounce(防抖)逻辑。
本文以电笛控制系统为例:话题 /electric_horn 发布 std_msgs::Bool,当电笛消息连续到来时只执行电笛控制;当最后一条消息超过 1 秒没有更新时,再触发 HeartBeat(true)。
不建议每条消息重建线程
用 std::thread 在每次回调里启动一个延迟线程,看起来可以实现延迟触发,但容易引入几个问题:
- 回调里
join()旧线程可能阻塞 ROS callback,影响消息处理。 lastMessageTime_如果由回调线程和延迟线程同时读写,需要互斥保护,否则存在数据竞争。- 一个共享的
stopDelayThread_标志很容易影响新旧线程的生命周期判断。
ROS 自身已经提供 Timer,更适合这种“延迟一段时间后执行一次”的场景。每次收到新消息时重启一个 one-shot timer;如果 1 秒内又收到消息,timer 会被重新计时;如果 1 秒内没有新消息,timer 回调触发。
实现思路
- 创建一个
ros::Timer,周期为 1 秒,设置为 one-shot,默认不自动启动。 - 每次收到
/electric_horn的true消息时,执行电笛逻辑,并重启 timer。 - 如果后续 1 秒内没有新消息,timer 回调执行
HeartBeat(true)。
代码实现
1 |
|
如果 ElectricHornControl() 可能阻塞,例如访问串口、网络或慢速硬件,建议把硬件操作投递到业务线程池,不要长时间占用 ROS 回调线程。即便如此,延迟触发本身仍建议由 ros::Timer 负责。
注意事项
- 如果使用
ros::AsyncSpinner或ros::MultiThreadedSpinner,共享的硬件状态仍然要用互斥锁或串行任务队列保护。 ros::Timer的回调默认也在 ROS callback 队列中执行,不适合放长时间阻塞任务。- 如果只是做“最后一次消息后延迟触发”,one-shot timer 比手动线程、轮询和 sleep 更直接。
总结
这类 ROS 消息延迟处理更适合用 Timer 实现。它避免了手动管理线程生命周期,也减少了数据竞争和回调阻塞风险。只有当业务本身确实需要后台长任务时,再额外引入线程池或任务队列。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Clang's Blog!
评论







