< >
Home » ROS与C++入门教程 » ROS与C++入门教程-Callbacks和Spinning

ROS与C++入门教程-Callbacks和Spinning

ROS与C++入门教程-Callbacks和Spinning

说明:

  • 介绍Callbacks(回调函数)和Spinning的由来及用途

Callbacks(回调函数)

  • roscpp不会为你的程序指定线程模型,这意味着roscpp使用线程在后台做网络管理、调度等,它永远不会暴露线程给应用程序。
  • roscpp可以做的,就是允许你的回调函数调用任意数量的线程。
  • 最终的结果是,不多做点工作,订阅、服务等回调将永远不会被调用。
  • 最常见的解决方案是ros::spin(),但你必须使用下面的一个选项。
  • 注意:回调队列/Spinning没有对roscpp内部网络通信的影响,他们只会影响用户的回调发生时。他们影响订阅队列的效果,因为你多快处理您的回调和信息多快能到达决定消息是否会丢弃。

单线程Spinning

  • 最简单常用的单线程Spinning版本是ros::spin():
ros::init(argc, argv, "my_node");
ros::NodeHandle nh;
ros::Subscriber sub = nh.subscribe(...);
...
ros::spin();
  • 在这个程序中,用户的回调函数在ros::spin()中被调用。
  • ros::spin()在节点关闭或ros::shutdown()或按下Ctrl-C才会返回
  • 另一个常见的模式是定期运行ros::spinonce():
ros::Rate r(10); // 10 hz
while (should_continue)
{
  ... do some work, publish some messages, etc. ...
  ros::spinOnce();
  r.sleep();
}
  • ros::spinOnce()在那个时间点,会调用所有的回调函数。
  • 执行一个自己的spin()函数很简单:
#include <ros/callback_queue.h>
ros::NodeHandle n;
while (ros::ok())
{
  ros::getGlobalCallbackQueue()->callAvailable(ros::WallDuration(0.1));
}
  • spinOnce() 类似:
#include <ros/callback_queue.h>
ros::getGlobalCallbackQueue()->callAvailable(ros::WallDuration(0));
  • 注:spin()和spinonce()真的意味着单线程应用程序,而不是从多个线程中调用的一次优化。

多线程Spinning

  • roscpp提供了一些内建方法来使用多线程的回调函数。
  • 有两个内建的方法:ros::MultiThreadedSpinner、ros::AsyncSpinner (since 0.10)
  • ros::MultiThreadedSpinner
  • MultiThreadedSpinner是阻塞式的spinner,类似ros::spin()。
  • 你可以在它的构造器上指定一定数量的线程数,但如果不指定或设置为0,则会在每个CPU上执行一个线程。
ros::MultiThreadedSpinner spinner(4); // Use 4 threads
spinner.spin(); // spin() will not return until the node has been shutdown
  • ros::AsyncSpinner (since 0.10)
  • AsyncSpinner API (Jade)
  • 很有用的线程spinner是AsyncSpinner,它不是阻塞式的spin()调用
  • 它有start() 和stop()函数,当它销毁时候就会自动关闭:
ros::AsyncSpinner spinner(4); // Use 4 threads
spinner.start();
ros::waitForShutdown();

CallbackQueue

#include <ros/callback_queue.h>
...
ros::CallbackQueue my_queue;
  • CallbackQueue 有两种方法调用回调函数: callAvailable() 和callOne().
  • callavailable()调用队列里所有的。
  • callone()只会调用回调在队列最旧的。
  • callAvailable()和callOne()都会有一个超时选项,在返回前,它会在超时时间内等待回调有效。
  • 如果是0,同时队列没有回调,则直接返回。
  • ROS 0.10 默认超时时间是0.1秒,ROS 0.11 默认是0。

高级:使用不同的回调队列

  • 你可以注意上面spin()执行的语句,有调用到 ros::getGlobalCallbackQueue(),默认所有的回调都会放到全局队列,由ros::spin() 处理。
  • roscpp也可以让你指定的自定义回调队列并独立处理:
    • 1.subscribe(), advertise(), advertiseService()
    • 2.NodeHandle
    1. 使用那些采用*option结构方式调用,查阅更多API
    1. 是更通用的方法:
ros::NodeHandle nh;
nh.setCallbackQueue(&my_callback_queue);
  • 这个可使用所有订阅,服务,定时器等,回调通过my_callback_queue而不是 roscpp的默认队列。意味着ros::spin() 和ros::spinOnce() 不会处理这些回调。你需要单独处理这些回调。
  • 你可以通过手工调用 ros::CallbackQueue::callAvailable() 和ros::CallbackQueue::callOne()方法处理。
my_callback_queue.callAvailable(ros::WallDuration());
// alternatively, .callOne(ros::WallDuration()) to only call a single callback instead of all available
  • 各种*Spinner对象可以使用指向回调队列的指针而不是默认值:

  • 示例代码:

    ros::AsyncSpinner spinner(0, &my_callback_queue);
    spinner.start();

  • 示例代码:

ros::MultiThreadedSpinner spinner(0);
spinner.spin(&my_callback_queue);

使用

  • 独立回调到不同队列是有用的,例如:
  • 长期运行的服务:指定一个服务自身的回调队列,在一个单独的线程提供服务,意味着这个服务是保证不会阻止其他回调。
  • 线程指定的计算量大的回调:类似前面,允许你线程化特定的回调,从而让你的程序保持简单的单线程回调。

纠错,疑问,交流: 请进入讨论区点击加入Q群

获取最新文章: 扫一扫右上角的二维码加入“创客智造”公众号


标签: ROS与C++入门教程