文章目录
-
- [1. 引言](#1. 引言)
- [2. 动作与话题、服务的对比](#2. 动作与话题、服务的对比)
- [3. 动作的应用场景](#3. 动作的应用场景)
- [4. 动作接口定义(.action 文件)](#4. 动作接口定义(.action 文件))
- [5. 动作核心概念与流程](#5. 动作核心概念与流程)
-
- [5.1 动作服务器 (Action Server)](#5.1 动作服务器 (Action Server))
- [5.2 动作客户端 (Action Client)](#5.2 动作客户端 (Action Client))
- [5.3 通信流程](#5.3 通信流程)
- [6. 动作服务器详解(Python & C++)](#6. 动作服务器详解(Python & C++))
- [7. 动作客户端详解(Python & C++)](#7. 动作客户端详解(Python & C++))
- [8. 功能测试方法](#8. 功能测试方法)
-
- [8.1 命令行工具测试](#8.1 命令行工具测试)
- [8.2 Python 脚本同时运行服务器和客户端](#8.2 Python 脚本同时运行服务器和客户端)
- [9. 高级主题](#9. 高级主题)
-
- [9.1 并发处理多个目标](#9.1 并发处理多个目标)
- [9.2 取消的最佳实践](#9.2 取消的最佳实践)
- [9.3 目标状态码](#9.3 目标状态码)
- [9.4 超时处理](#9.4 超时处理)
- [9.5 QoS 设置](#9.5 QoS 设置)
- [9.6 在组件(Component)中使用动作](#9.6 在组件(Component)中使用动作)
- [10. 最佳实践与常见问题](#10. 最佳实践与常见问题)
-
- [10.1 服务器端](#10.1 服务器端)
- [10.2 客户端](#10.2 客户端)
- [10.3 常见陷阱](#10.3 常见陷阱)
- [11. 总结](#11. 总结)
1. 引言
ROS2 动作(Action)是一种基于目标(Goal) 、**反馈(Feedback)和 结果(Result)的异步通信机制,专门用于处理长时间运行的任务。它内部使用 话题(Topic)和服务(Service)**的组合来实现,提供了目标、反馈、结果和取消等完整功能,是机器人导航、机械臂控制等复杂场景的核心通信方式。
本文将从零开始,全面覆盖 ROS2 动作编程的所有知识点,包括与其他通信机制的对比、应用场景、接口定义、服务器与客户端的详细实现(Python 和 C++,每行代码带注释)、测试方法、高级主题及最佳实践。
2. 动作与话题、服务的对比
| 特性 | 话题 (Topic) | 服务 (Service) | 动作 (Action) |
|---|---|---|---|
| 通信模式 | 发布/订阅 (Publisher/Subscriber) | 请求/响应 (Request/Response) | 目标/结果/反馈 (Goal/Result/Feedback) |
| 同步性 | 异步,无阻塞 | 同步或异步,客户端等待响应 | 异步,客户端可监控进度 |
| 反馈 | 无 | 无 | 有,可定期返回进度 |
| 取消 | 不适用 | 通常不支持(可自行实现) | 内置取消机制 |
| 适用场景 | 持续数据流,如传感器数据 | 一次性计算或任务,如查询状态 | 长时间任务,需反馈和可取消,如导航 |
| 通信方向 | 单向流 | 双向一次 | 双向多次(目标、反馈、结果) |
| 底层实现 | 基于 DDS 主题 | 基于 DDS 服务 | 基于话题+服务组合 |
详细说明:
- 话题:发布者持续发送数据,订阅者被动接收,无确认和反馈。
- 服务:客户端发送请求,服务器处理后返回响应,适合短时操作,客户端通常阻塞等待。
- 动作:客户端发送目标,服务器执行过程中发布反馈,最终返回结果,并可随时取消,适合耗时任务。
3. 动作的应用场景
- 机器人导航:发送目标点,机器人规划路径并移动,期间反馈当前位置、状态,可随时取消。
- 机械臂运动规划:请求机械臂移动到某个姿态,规划过程可能耗时,执行过程中反馈关节角度。
- 文件下载/上传:请求下载一个大文件,反馈下载进度,可取消。
- 长时间计算的算法:如 SLAM 建图、点云配准,需要中间结果或进度。
- 交互式任务:需要用户确认或干预的任务,如"移动到A点,然后等待指令"。
4. 动作接口定义(.action 文件)
动作接口使用 .action 文件定义,包含三个部分,用 --- 分隔:
# Goal(目标)
int32 order # 例如:请求计算斐波那契数列的阶数
---
# Result(结果)
int32[] sequence # 返回完整的斐波那契数列
---
# Feedback(反馈)
int32[] partial_sequence # 当前计算的中间序列
编译后会在对应语言中生成消息类:
- Python:
Fibonacci.Goal、Fibonacci.Result、Fibonacci.Feedback - C++:
Fibonacci::Goal、Fibonacci::Result、Fibonacci::Feedback
支持所有 ROS2 消息类型(基本类型、数组、嵌套消息)。
5. 动作核心概念与流程
5.1 动作服务器 (Action Server)
- 职责:接收客户端的目标、执行任务、发布反馈、返回结果、处理取消请求。
- 关键组件 :
- 目标回调:决定是否接受新目标。
- 执行回调:实际执行任务的函数。
- 取消回调:处理取消请求。
- 反馈发布:定期发布进度。
- 结果返回:任务完成时返回结果,并标记状态。
5.2 动作客户端 (Action Client)
- 职责:发送目标、接收反馈、获取最终结果、取消目标。
- 关键组件 :
- 等待服务器:确保服务器可用。
- 发送目标:可附带反馈回调和结果回调。
- 反馈回调:处理服务器发来的反馈。
- 结果回调:处理最终结果。
- 取消目标:主动取消正在执行的目标。
5.3 通信流程
- 客户端发送目标(Goal)到服务器。
- 服务器通过目标回调决定接受或拒绝。
- 若接受,服务器开始执行任务,并定期发布反馈(Feedback)。
- 客户端通过反馈回调接收进度。
- 任务完成时,服务器返回结果(Result),客户端通过结果回调接收。
- 客户端可随时发送取消请求,服务器应响应。
6. 动作服务器详解(Python & C++)
6.1 Python 动作服务器
关键函数/类说明
-
ActionServer构造函数:pythonActionServer(node, action_type, action_name, execute_callback, goal_callback=None, cancel_callback=None, handle_accepted_callback=None)node: ROS2 节点实例。action_type: 动作类型,如Fibonacci。action_name: 动作名称,字符串。execute_callback: 执行回调函数,接收goal_handle,返回Result对象。goal_callback: 可选,目标回调,接收goal_request,返回GoalResponse.ACCEPT或REJECT。cancel_callback: 可选,取消回调,接收goal_handle,返回CancelResponse.ACCEPT或REJECT。handle_accepted_callback: 可选,目标被接受后回调,接收goal_handle,通常用于异步启动执行。
-
ServerGoalHandle对象方法:is_cancel_requested: 属性,检查是否收到取消请求。publish_feedback(feedback_msg): 发布反馈。succeed(result=None): 标记目标成功,可选附带结果。abort(result=None): 标记目标中止。canceled(result=None): 标记目标取消。get_result(): 获取结果(一般不直接调用)。
-
asyncio:用于异步操作,如asyncio.sleep()模拟耗时。
完整代码示例(Fibonacci 动作服务器)
python
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rclpy.action import ActionServer
from rclpy.action.server import ServerGoalHandle
from example_interfaces.action import Fibonacci
import asyncio
class FibonacciActionServer(Node):
def __init__(self):
super().__init__('fibonacci_action_server')
# 创建动作服务器
self._action_server = ActionServer(
self,
Fibonacci,
'fibonacci',
execute_callback=self.execute_callback,
goal_callback=self.goal_callback,
cancel_callback=self.cancel_callback,
handle_accepted_callback=self.handle_accepted_callback
)
self.get_logger().info('Fibonacci action server started')
def goal_callback(self, goal_request: Fibonacci.Goal):
"""目标回调:决定是否接受目标"""
self.get_logger().info(f'Received goal request with order: {goal_request.order}')
if goal_request.order < 0:
self.get_logger().warn('Rejecting goal: order must be >= 0')
return rclpy.action.GoalResponse.REJECT
return rclpy.action.GoalResponse.ACCEPT
def cancel_callback(self, goal_handle: ServerGoalHandle):
"""取消回调:决定是否接受取消请求"""
self.get_logger().info('Received cancel request')
return rclpy.action.CancelResponse.ACCEPT
def handle_accepted_callback(self, goal_handle: ServerGoalHandle):
"""目标被接受后的回调:异步启动执行"""
self.get_logger().info('Goal accepted, starting execution...')
asyncio.create_task(self.execute_callback(goal_handle))
async def execute_callback(self, goal_handle: ServerGoalHandle):
"""执行回调:实际执行任务"""
self.get_logger().info('Executing goal...')
goal_request = goal_handle.request
order = goal_request.order
feedback_msg = Fibonacci.Feedback()
feedback_msg.partial_sequence = [0, 1]
sequence = [0, 1]
if order <= 1:
result = Fibonacci.Result()
result.sequence = sequence[:order+1]
goal_handle.succeed()
return result
for i in range(1, order):
if goal_handle.is_cancel_requested:
self.get_logger().info('Goal canceled by client')
result = Fibonacci.Result()
result.sequence = sequence
goal_handle.canceled(result)
return result
next_num = sequence[i] + sequence[i-1]
sequence.append(next_num)
feedback_msg.partial_sequence = sequence
goal_handle.publish_feedback(feedback_msg)
self.get_logger().info(f'Publishing feedback: {sequence}')
await asyncio.sleep(1.0)
result = Fibonacci.Result()
result.sequence = sequence
goal_handle.succeed()
self.get_logger().info('Goal succeeded')
return result
def main(args=None):
rclpy.init(args=args)
node = FibonacciActionServer()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
6.2 C++ 动作服务器
关键函数/类说明
-
rclcpp_action::create_server<ActionT>:cppauto server = rclcpp_action::create_server<ActionT>( node, name, std::bind(&handle_goal, this, _1, _2), std::bind(&handle_cancel, this, _1), std::bind(&handle_accepted, this, _1));node: 节点共享指针。name: 动作名称。- 三个回调:
handle_goal、handle_cancel、handle_accepted。
-
GoalHandleFibonacci:rclcpp_action::ServerGoalHandle<Fibonacci>的别名。 -
goal_handle方法:get_goal(): 获取目标消息(const Fibonacci::Goal&)。publish_feedback(feedback): 发布反馈(std::shared_ptr<Fibonacci::Feedback>)。is_canceling(): 检查是否收到取消请求。succeed(result): 标记成功(std::shared_ptr<Fibonacci::Result>)。canceled(result): 标记取消。abort(result): 标记中止。
-
rclcpp::Rate:用于固定频率循环。
完整代码示例(Fibonacci 动作服务器)
cpp
#include <memory>
#include <thread>
#include <chrono>
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <example_interfaces/action/fibonacci.hpp>
using Fibonacci = example_interfaces::action::Fibonacci;
using GoalHandleFibonacci = rclcpp_action::ServerGoalHandle<Fibonacci>;
using namespace std::placeholders;
class FibonacciActionServer : public rclcpp::Node
{
public:
FibonacciActionServer() : Node("fibonacci_action_server")
{
action_server_ = rclcpp_action::create_server<Fibonacci>(
this,
"fibonacci",
std::bind(&FibonacciActionServer::handle_goal, this, _1, _2),
std::bind(&FibonacciActionServer::handle_cancel, this, _1),
std::bind(&FibonacciActionServer::handle_accepted, this, _1));
RCLCPP_INFO(this->get_logger(), "Fibonacci action server started");
}
private:
rclcpp_action::Server<Fibonacci>::SharedPtr action_server_;
rclcpp_action::GoalResponse handle_goal(
const rclcpp_action::GoalUUID & uuid,
std::shared_ptr<const Fibonacci::Goal> goal)
{
RCLCPP_INFO(this->get_logger(), "Received goal request with order %d", goal->order);
if (goal->order < 0) {
RCLCPP_WARN(this->get_logger(), "Rejecting goal: order must be >= 0");
return rclcpp_action::GoalResponse::REJECT;
}
return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE;
}
rclcpp_action::CancelResponse handle_cancel(
const std::shared_ptr<GoalHandleFibonacci> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Received cancel request");
return rclcpp_action::CancelResponse::ACCEPT;
}
void handle_accepted(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
{
std::thread{std::bind(&FibonacciActionServer::execute, this, _1), goal_handle}.detach();
}
void execute(const std::shared_ptr<GoalHandleFibonacci> goal_handle)
{
RCLCPP_INFO(this->get_logger(), "Executing goal...");
const auto goal = goal_handle->get_goal();
auto feedback = std::make_shared<Fibonacci::Feedback>();
auto result = std::make_shared<Fibonacci::Result>();
std::vector<int> sequence = {0, 1};
feedback->partial_sequence = sequence;
goal_handle->publish_feedback(feedback);
rclcpp::Rate rate(1);
for (int i = 1; i < goal->order; ++i) {
if (goal_handle->is_canceling()) {
result->sequence = sequence;
goal_handle->canceled(result);
RCLCPP_INFO(this->get_logger(), "Goal canceled");
return;
}
int next = sequence[i] + sequence[i-1];
sequence.push_back(next);
feedback->partial_sequence = sequence;
goal_handle->publish_feedback(feedback);
RCLCPP_INFO(this->get_logger(), "Publishing feedback: [%s]",
vector_to_string(sequence).c_str());
rate.sleep();
}
result->sequence = sequence;
goal_handle->succeed(result);
RCLCPP_INFO(this->get_logger(), "Goal succeeded");
}
std::string vector_to_string(const std::vector<int> &vec)
{
std::stringstream ss;
for (size_t i = 0; i < vec.size(); ++i) {
if (i != 0) ss << ", ";
ss << vec[i];
}
return ss.str();
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto node = std::make_shared<FibonacciActionServer>();
rclcpp::spin(node);
rclcpp::shutdown();
return 0;
}
7. 动作客户端详解(Python & C++)
7.1 Python 动作客户端
关键函数/类说明
-
ActionClient构造函数:pythonActionClient(node, action_type, action_name)node: ROS2 节点实例。action_type: 动作类型。action_name: 动作名称。
-
wait_for_server(timeout_sec):等待服务器可用,返回布尔值。 -
send_goal_async(goal_msg, feedback_callback=None):- 异步发送目标,返回
Future,当服务器接受/拒绝目标时完成。 goal_msg: 目标消息实例。feedback_callback: 可选,反馈回调函数,接收feedback_msg。
- 异步发送目标,返回
-
ClientGoalHandle对象方法:accepted: 属性,布尔值,表示目标是否被接受。get_result_async(): 异步获取最终结果,返回Future,结果可用时完成。cancel_goal_async(): 异步取消目标。
-
反馈回调:
pythondef feedback_callback(feedback_msg): feedback = feedback_msg.feedback # 获取实际的反馈消息 -
结果回调 :
通过
future.result()得到WrappedResult,包含status和result。
完整代码示例(Fibonacci 动作客户端)
python
#!/usr/bin/env python3
import rclpy
from rclpy.node import Node
from rclpy.action import ActionClient
from example_interfaces.action import Fibonacci
class FibonacciActionClient(Node):
def __init__(self):
super().__init__('fibonacci_action_client')
self._action_client = ActionClient(self, Fibonacci, 'fibonacci')
self.get_logger().info('Fibonacci action client started')
def send_goal(self, order: int):
self.get_logger().info('Waiting for action server...')
if not self._action_client.wait_for_server(timeout_sec=5.0):
self.get_logger().error('Action server not available after waiting')
return
goal_msg = Fibonacci.Goal()
goal_msg.order = order
self.get_logger().info(f'Sending goal with order: {order}')
send_goal_future = self._action_client.send_goal_async(
goal_msg,
feedback_callback=self.feedback_callback
)
send_goal_future.add_done_callback(self.goal_response_callback)
def goal_response_callback(self, future):
goal_handle = future.result()
if not goal_handle.accepted:
self.get_logger().info('Goal rejected by server')
return
self.get_logger().info('Goal accepted by server')
self._get_result_future = goal_handle.get_result_async()
self._get_result_future.add_done_callback(self.get_result_callback)
def feedback_callback(self, feedback_msg):
feedback = feedback_msg.feedback
self.get_logger().info(f'Received feedback: {feedback.partial_sequence}')
def get_result_callback(self, future):
wrapped_result = future.result()
result = wrapped_result.result
status = wrapped_result.status
if status == 4:
self.get_logger().info(f'Goal succeeded with result: {result.sequence}')
elif status == 5:
self.get_logger().error('Goal aborted')
elif status == 6:
self.get_logger().warn('Goal canceled')
else:
self.get_logger().info(f'Goal finished with status: {status}')
rclpy.shutdown()
def main(args=None):
rclpy.init(args=args)
node = FibonacciActionClient()
node.send_goal(10)
rclpy.spin(node)
if __name__ == '__main__':
main()
7.2 C++ 动作客户端
关键函数/类说明
rclcpp_action::create_client<ActionT>(node, name):创建客户端。wait_for_action_server(timeout):等待服务器可用,返回布尔值。async_send_goal(goal_msg, options):goal_msg: 目标消息(如Fibonacci::Goal)。options:SendGoalOptions结构,可包含:goal_response_callback: 目标响应回调。feedback_callback: 反馈回调。result_callback: 结果回调。
- 回调签名 :
goal_response_callback:void (const GoalHandleFibonacci::SharedPtr &)feedback_callback:void (GoalHandleFibonacci::SharedPtr, const std::shared_ptr<const Fibonacci::Feedback>)result_callback:void (const GoalHandleFibonacci::WrappedResult &)
GoalHandleFibonacci::WrappedResult:包含result和status字段。
完整代码示例(Fibonacci 动作客户端)
cpp
#include <rclcpp/rclcpp.hpp>
#include <rclcpp_action/rclcpp_action.hpp>
#include <example_interfaces/action/fibonacci.hpp>
using Fibonacci = example_interfaces::action::Fibonacci;
using GoalHandleFibonacci = rclcpp_action::ClientGoalHandle<Fibonacci>;
using namespace std::placeholders;
class FibonacciActionClient : public rclcpp::Node
{
public:
FibonacciActionClient() : Node("fibonacci_action_client")
{
client_ = rclcpp_action::create_client<Fibonacci>(this, "fibonacci");
RCLCPP_INFO(this->get_logger(), "Fibonacci action client started");
}
void send_goal(int order)
{
if (!client_->wait_for_action_server(std::chrono::seconds(10))) {
RCLCPP_ERROR(this->get_logger(), "Action server not available after waiting");
return;
}
auto goal_msg = Fibonacci::Goal();
goal_msg.order = order;
RCLCPP_INFO(this->get_logger(), "Sending goal with order %d", order);
auto send_goal_options = rclcpp_action::Client<Fibonacci>::SendGoalOptions();
send_goal_options.goal_response_callback =
std::bind(&FibonacciActionClient::goal_response_callback, this, _1);
send_goal_options.feedback_callback =
std::bind(&FibonacciActionClient::feedback_callback, this, _1, _2);
send_goal_options.result_callback =
std::bind(&FibonacciActionClient::result_callback, this, _1);
client_->async_send_goal(goal_msg, send_goal_options);
}
private:
rclcpp_action::Client<Fibonacci>::SharedPtr client_;
void goal_response_callback(const GoalHandleFibonacci::SharedPtr & goal_handle)
{
if (!goal_handle) {
RCLCPP_ERROR(this->get_logger(), "Goal was rejected by server");
} else {
RCLCPP_INFO(this->get_logger(), "Goal accepted by server, waiting for result...");
}
}
void feedback_callback(
GoalHandleFibonacci::SharedPtr,
const std::shared_ptr<const Fibonacci::Feedback> feedback)
{
std::stringstream ss;
for (auto num : feedback->partial_sequence) {
if (ss.tellp() != 0) ss << ", ";
ss << num;
}
RCLCPP_INFO(this->get_logger(), "Received feedback: [%s]", ss.str().c_str());
}
void result_callback(const GoalHandleFibonacci::WrappedResult & result)
{
switch (result.code) {
case rclcpp_action::ResultCode::SUCCEEDED:
break;
case rclcpp_action::ResultCode::ABORTED:
RCLCPP_ERROR(this->get_logger(), "Goal was aborted");
return;
case rclcpp_action::ResultCode::CANCELED:
RCLCPP_ERROR(this->get_logger(), "Goal was canceled");
return;
default:
RCLCPP_ERROR(this->get_logger(), "Unknown result code");
return;
}
std::stringstream ss;
for (auto num : result.result->sequence) {
if (ss.tellp() != 0) ss << ", ";
ss << num;
}
RCLCPP_INFO(this->get_logger(), "Goal completed with result: [%s]", ss.str().c_str());
rclcpp::shutdown();
}
};
int main(int argc, char ** argv)
{
rclcpp::init(argc, argv);
auto client = std::make_shared<FibonacciActionClient>();
client->send_goal(10);
rclcpp::spin(client);
rclcpp::shutdown();
return 0;
}
8. 功能测试方法
8.1 命令行工具测试
bash
# 启动服务器(任选语言)
python3 fibonacci_action_server.py
# 查看动作列表
ros2 action list
# 查看动作类型
ros2 action list -t
# 发送目标并查看反馈
ros2 action send_goal /fibonacci example_interfaces/action/Fibonacci "{order: 5}" --feedback
# 获取动作信息
ros2 action info /fibonacci
8.2 Python 脚本同时运行服务器和客户端
python
#!/usr/bin/env python3
import rclpy
from rclpy.executors import MultiThreadedExecutor
from fibonacci_action_server import FibonacciActionServer
from fibonacci_action_client import FibonacciActionClient
def main(args=None):
rclpy.init(args=args)
server_node = FibonacciActionServer()
client_node = FibonacciActionClient()
executor = MultiThreadedExecutor()
executor.add_node(server_node)
executor.add_node(client_node)
client_node.send_goal(10)
try:
executor.spin()
finally:
executor.shutdown()
server_node.destroy_node()
client_node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
9. 高级主题
9.1 并发处理多个目标
- Python :在
handle_accepted_callback中将目标放入asyncio.Queue,使用多个工作协程处理。 - C++:使用线程池,每个目标分配一个线程执行,注意线程安全。
9.2 取消的最佳实践
- 服务器定期检查取消标志(
is_cancel_requested/is_canceling()),尽快停止工作。 - 客户端可通过
goal_handle.cancel_goal_async()发送取消请求。 - 取消后服务器应调用
canceled(result)并返回部分结果。
9.3 目标状态码
预定义状态码(rclpy.action.GoalStatus 或 rclcpp_action::GoalStatus):
STATUS_UNKNOWN = 0STATUS_ACCEPTED = 1STATUS_EXECUTING = 2STATUS_CANCELING = 3STATUS_SUCCEEDED = 4STATUS_ABORTED = 5STATUS_CANCELED = 6
9.4 超时处理
- 客户端等待服务器可设置超时:
wait_for_server(timeout)。 - 执行超时:客户端可启动定时器,超时后取消目标。
9.5 QoS 设置
动作底层话题和服务可使用自定义 QoS。在 Python 中通过 ActionServer 的参数设置,C++ 中通过重载的 create_server / create_client 指定。
9.6 在组件(Component)中使用动作
动作服务器和客户端均可用于节点组件,只需在组件的构造函数中创建。注意多线程环境下的安全性。
10. 最佳实践与常见问题
10.1 服务器端
- 目标回调:快速返回,避免阻塞。
- 执行回调:设计为可中断的循环,定期检查取消标志。
- 反馈频率:不宜过高,避免网络拥塞。
- 并发:如需并发,使用线程池或协程,并注意数据竞争。
- 资源清理:取消或失败时清理资源。
10.2 客户端
- 等待服务器:发送目标前确保服务器可用。
- 处理拒绝:目标可能被拒绝,需有备选逻辑。
- 取消处理:在长时间等待时提供取消选项。
- 超时:自行实现超时机制。
10.3 常见陷阱
- 服务器未检查取消标志:导致取消无法及时响应。
- 反馈发布过快:可能引发网络问题。
- 目标回调中执行耗时操作:阻塞服务器处理其他目标。
- C++ 线程未分离 :
handle_accepted中启动线程后需detach或管理生命周期。 - Python 协程未正确处理 :使用
asyncio.create_task而非await,避免阻塞回调。 - 多目标并发时的资源竞争:使用互斥锁保护共享数据。
11. 总结
ROS2 动作是一种功能强大的通信机制,专门用于长时间运行的任务,提供了目标、反馈、结果和取消等完整功能。本文全面介绍了动作与话题、服务的区别、应用场景、接口定义、服务器与客户端的详细实现(Python 和 C++,每行代码带注释)、测试方法、高级主题及最佳实践。掌握这些知识点,开发者可以轻松实现复杂的机器人行为,并构建高效、可靠的 ROS2 应用。
参考文献:ROS2 官方文档(https://docs.ros.org/)