> ## Documentation Index
> Fetch the complete documentation index at: https://docs.wuji.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# 数据订阅：异步

## 概述

本篇实践介绍如何用 `async/await` 订阅 Wuji Hand 2 的 `joint_states` 实时状态流，并在协程中按顺序读取关节状态。应用场景包括接入基于 `asyncio` 的应用、并发读取多台设备的数据，以及与其他异步 I/O 任务协同运行。

对应的 Python 示例为 [1.subscribe\_async.py](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_hand_2/1.subscribe_async.py)。 字段定义见 [JointStateFrame 与 JointStateEntry](/docs/wuji-hand/latest/sdk-reference/joint-control#数据类型)，本页说明集成流程和运行时行为。

<Info>
  C 接口没有对应的异步形式。C 侧订阅统一走回调，见[数据订阅：回调](/docs/wuji-hand/latest/sdk-practice/subscribe-callback)。
</Info>

## 前置条件

### 硬件

| 项目 | 要求 |
| - | - |
| Wuji Hand 2 | 1 只 |
| 连接方式 | 以太网 |

### 软件

| 项目 | 要求 |
| - | - |
| 操作系统 | Ubuntu 22.04 LTS 及以上版本 |
| Wuji SDK | v2026.8.31 或更高版本 |
| Python | 3.10 及以上版本 |

```bash theme={null}
pip install wuji-sdk
```

## 数据流

```mermaid theme={null}
flowchart TD
    SCAN["扫描并筛选 Wuji Hand 2<br/>scan()"]
    CONNECT["按序列号连接设备<br/>connect()"]
    SUB["创建 joint_states 订阅<br/>hand.joint_states().subscribe()"]
    LOOP["asyncio.gather() 运行消费协程"]
    RECV["await sub.recv_async()<br/>取得下一帧 JointStateFrame"]
    PROCESS["读取 joints 并处理关节状态"]
    CLEAN["任务结束后断开所有设备<br/>manager.disconnect_all()"]

    SCAN --> CONNECT --> SUB --> LOOP --> RECV --> PROCESS
    PROCESS -->|继续读取| RECV
    LOOP --> CLEAN
```

`subscribe()` 拿到的句柄同时支持两种取法：`await recv_async()` 异步取，`recv()` 同步取。帧结构与回调式完全一致。

## 集成步骤

完整实现见[Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_hand_2/1.subscribe_async.py)。

### 1. 发现并连接设备

使用 `SdkManager.instance()` 获取管理器，通过 `scan()` 筛选 Wuji Hand 2，确认结果非空后再连接。[Python 示例](https://github.com/wuji-technology/wuji-sdk/blob/main/examples/python/wuji_hand_2/1.subscribe_async.py) 会连接发现的全部 Wuji Hand 2，下面展示连接其中一台的关键调用。

```python theme={null}
manager = SdkManager.instance()
hands = [d for d in manager.scan() if d.device_type == DeviceType.WujiHand2]
if not hands:
    raise RuntimeError("No Wuji Hand 2 found")
hand = manager.connect(sn=hands[0].sn, device_name="hand_0")
```

### 2. 在协程中读取状态帧

`subscribe()` 建立订阅，`recv_async()` 等待下一帧。读取当前帧中的关节列表，通过 `nid` 识别关节。将订阅的关闭操作放入 `finally`，使协程取消时也能释放资源。

```python theme={null}
async def consume(hand):
    sub = hand.joint_states().subscribe()
    try:
        while True:
            frame = await sub.recv_async()
            for joint in frame.joints:
                print(joint.nid, joint.position, joint.velocity, joint.effort)
    finally:
        sub.close()
```

### 3. 运行消费任务并断开设备

在 `main()` 协程中等待消费任务。多设备时，为每台设备创建一个任务并交给 `asyncio.gather()`。任务结束后断开连接，入口使用 `asyncio.run(main())`。这条路径只读取状态，无需使能电机。

```python theme={null}
try:
    await asyncio.gather(consume(hand))
finally:
    manager.disconnect_all()
```

## 运行与输出

```bash theme={null}
python examples/python/wuji_hand_2/1.subscribe_async.py
```

```text theme={null}
Connected: WH2KA00XXXXXXXXX (20 joints online)
[hand_0][JointState] seq=147508 t=210677379743us frame='r_wrist' thumb_pos=['+0.003', '-0.018', '+0.021', '+0.056']
```

### 实测数据

在 20 关节全在线的右手 Wuji Hand 2 上实跑：

| 指标 | 实测 |
| - | - |
| `recv_async()` 吞吐 | **1088 Hz**（3 秒 3265 帧） |
| 同一句柄 `recv()` 同步取 | 1 秒 1091 帧 |
| `frame_id` | `r_wrist` |
| `joints` 长度 | 20 |

异步与同步两种取法的吞吐一致，都跟得上设备约 1 kHz 的广播。选哪种取决于应用结构，不是性能问题。

## 注意事项

| 分支点 | 表现 | 处理 |
| - | - | - |
| 队列为空 | `recv_async()` 挂起当前协程，让出事件循环 | 正常行为，不要用忙等 |
| 同步 `recv()` 队列为空 | 立即返回 `None`，不阻塞 | 循环里配合 `recv()` 要自己控制节奏 |
| 设备断连 | `recv_async()` 抛 `WujiException` | v2026.8.17 起是立即抛出，此前可能返回缓冲帧或 `None` |
| 多台设备 | 示例连接扫到的每一台，用 `asyncio.gather()` 并发 | 共用网络要改成指定 SN |
| 协程被取消 | `gather` 抛出，进入 `finally` 断开设备 | 示例靠 `finally` 里的 `disconnect_all()` 收尾 |

## 扩展用法

### 与回调式的取舍

| | 异步 `recv_async()` | 回调 `subscribe_with_callback()` |
| - | - | - |
| 执行位置 | 调用方的事件循环 | SDK 工作线程 |
| 背压 | 队列积压可感知，可主动丢帧 | 回调阻塞会直接拖慢工作线程 |
| 适用 | 应用已在 asyncio 上 | 简单转发、写队列 |
| C 接口 | 无 | 有 |

### 多设备并发

每台设备一个协程，`asyncio.gather()` 并发即可。设备之间互不阻塞。

### 降低输出率

约 1 kHz 对多数应用偏高。用同一句柄的 `set_rate()` 降频，注意有固件版本要求，见[输出率调节](/docs/wuji-hand/latest/sdk-practice/stream-rate)。

## 相关内容

* [数据订阅：回调](/docs/wuji-hand/latest/sdk-practice/subscribe-callback)：回调式写法，以及 C 接口的唯一形式
* [输出率调节](/docs/wuji-hand/latest/sdk-practice/stream-rate)：降低固件推送频率
* [数据订阅机制](/docs/wuji-sdk/data-subscription/)：订阅模型与生命周期


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.