文章目录
① 介绍
② 同步调用
死锁
③ 异步调用
④ 总结
① 介绍
本指南旨在警告用户与Python 同步服务客户端 call()API相关的风险。在同步调用服务时,很容易错误地导致死锁,因此我们不建议使用call()。
官方虽然提供了demo,但是还是不推荐使用,建议避免同步调用,所以本指南还将介绍推荐的替代方法异步调用(call_async())的功能和用法。
C++服务调用API仅在异步中可用,因此本指南中的比较和示例与Python服务和客户端有关。
② 同步调用
同步客户端会在发送请求直到收到响应这段时间完全阻塞调用call的线程,这个线程基本上在调用时属于独占了。调用儿断时间不确定,但是调用完成立马就返回了。
下面这个例子就是同步调用的,可以参看之前的教程
import sys
from threading import Thread
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalClientSync(Node):
def __init__(self):
super().__init__('minimal_client_sync')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddTwoInts.Request()
def send_request(self):
self.req.a = int(sys.argv[1])
self.req.b = int(sys.argv[2])
return self.cli.call(self.req)
# This only works because rclpy.spin() is called in a separate thread below.
# Another configuration, like spinning later in main() or calling this method from a timer callback, would result in a deadlock.
def main():
rclpy.init()
minimal_client = MinimalClientSync()
spin_thread = Thread(target=rclpy.spin, args=(minimal_client,))
spin_thread.start()
response = minimal_client.send_request()
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
这里面spin()
函数需要放到单独的线程来执行,因为call()
会阻塞主线程。
死锁
产生死锁的方式有下面两种(这么难用还是不要用了,用异步吧):
- 没有出子线程执行
spin()
send_request()
位于回调中
下面是第二种死锁的例子
def trigger_request(msg):
response = minimal_client.send_request() # This will cause deadlock
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
subscription = minimal_client.create_subscription(String, 'trigger', trigger_request, 10)
rclpy.spin(minimal_client)
死锁很难定位,也不报错,很烦,最好就是不用这种方式
③ 异步调用
异步调用就是相当安全了,不会存在阻塞ros进程或者非ros进程的风险,异步调用完成之后,会立马返回future
,只需要等待就行,直接看看几种实现吧
import sys
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddTwoInts.Request()
def send_request(self):
self.req.a = int(sys.argv[1])
self.req.b = int(sys.argv[2])
self.future = self.cli.call_async(self.req)
def main(args=None):
rclpy.init(args=args)
minimal_client = MinimalClientAsync()
minimal_client.send_request()
while rclpy.ok():
rclpy.spin_once(minimal_client)
if minimal_client.future.done():
try:
response = minimal_client.future.result()
except Exception as e:
minimal_client.get_logger().info(
'Service call failed %r' % (e,))
else:
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(minimal_client.req.a, minimal_client.req.b, response.sum))
break
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
这个是判断future
是否done
def main(args=None):
rclpy.init(args=args)
node = rclpy.create_node('minimal_client')
# Node's default callback group is mutually exclusive. This would prevent the client response
# from being processed until the timer callback finished, but the timer callback in this
# example is waiting for the client response
cb_group = ReentrantCallbackGroup()
cli = node.create_client(AddTwoInts, 'add_two_ints', callback_group=cb_group)
did_run = False
did_get_result = False
async def call_service():
nonlocal cli, node, did_run, did_get_result
did_run = True
try:
req = AddTwoInts.Request()
req.a = 41
req.b = 1
future = cli.call_async(req)
try:
result = await future
except Exception as e:
node.get_logger().info('Service call failed %r' % (e,))
else:
node.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(req.a, req.b, result.sum))
finally:
did_get_result = True
while not cli.wait_for_service(timeout_sec=1.0):
node.get_logger().info('service not available, waiting again...')
timer = node.create_timer(0.5, call_service, callback_group=cb_group)
while rclpy.ok() and not did_run:
rclpy.spin_once(node)
这种是await,python的语法了
④ 总结
别用同步回调,会死锁了,看看异步多好用
评论(0)
您还未登录,请登录后发表或查看评论