① 介紹
本指南旨在警告用戶與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的語法了
④ 總結
別用同步回調,會死鎖了,看看異步多好用