HarmonyOS Sendable 对象模型:跨并发实例零拷贝共享
适用版本:HarmonyOS 6.1(API 12)及以上
验证环境:Pura 90 Pro 模拟器(HarmonyOS 6.1.1,API 24)
关键 API:@Sendable装饰器、collections.Array<T>、taskpool
前言
前两篇介绍了 TaskPool 和 Worker,它们传递数据都依赖序列化——对象被克隆一份送入子线程,子线程拿到的是副本。当数据量大(MB 级对象、大数组)时,序列化本身就会消耗可观的时间和内存。
@Sendable 提供了另一条路:标注了该装饰器的类,其实例可以以引用方式在并发实例间传递,不需要序列化,跨线程访问的是同一个对象。本文演示其正确写法和编译器规则。
核心规则
1. 属性类型必须全部是 Sendable 类型
| 类型 | 是否 Sendable |
|---|---|
number / string / boolean / null / undefined |
✅ |
collections.Array<SendableType> |
✅ |
collections.Map<K, V> |
✅ |
@Sendable class 实例 |
✅ |
普通 number[] / string[] |
❌ 编译报错 |
普通 class 实例 |
❌ 编译报错 |
| 函数 / 闭包 | ❌ 编译报错 |
2. @Sendable 类不允许继承普通类
// ✅ 正确:继承另一个 @Sendable 类
@Sendable
class Base { id: number = 0 }
@Sendable
class Child extends Base { name: string = '' }
// ❌ 错误:继承普通类
class NormalBase {}
@Sendable
class WrongChild extends NormalBase {} // 编译报错
完整代码
import { taskpool, collections } from '@kit.ArkTS'
// @Sendable 类:所有属性必须是 Sendable 类型
@Sendable
class DataPacket {
id: number = 0
label: string = ''
precomputedSum: number = 0
count: number = 0
constructor(id: number, label: string, count: number) {
this.id = id
this.label = label
this.count = count
this.precomputedSum = count * (count - 1) // 预计算 0+2+4+...
}
}
// @Sendable 对象作为 TaskPool 参数(引用传递)
@Concurrent
function processPacket(packet: DataPacket, workerTag: string): string {
const verify = packet.precomputedSum === packet.count * (packet.count - 1)
return `[${workerTag}] id=${packet.id} label="${packet.label}" ` +
`sum=${packet.precomputedSum} count=${packet.count} verify=${verify}`
}
// 对比:普通参数(序列化拷贝传递)
@Concurrent
function processPlain(id: number, label: string, sum: number,
count: number, workerTag: string): string {
const verify = sum === count * (count - 1)
return `[${workerTag}] id=${id} label="${label}" ` +
`sum=${sum} count=${count} verify=${verify} [plain copy]`
}
// 主线程:并发发送两个任务
const packet = new DataPacket(42, 'HarmonyOS-Sendable', 100000)
const [res1, res2] = await Promise.all([
taskpool.execute(new taskpool.Task(processPacket, packet, 'Worker-A')),
taskpool.execute(new taskpool.Task(processPlain,
packet.id, packet.label, packet.precomputedSum, packet.count, 'Worker-B')),
])
模拟器运行截图
初始状态

执行完成
两路并发均成功执行,verify=true 证明数据在子线程中被正确读取。

实测结果:
| 方式 | 数据量 | 结果 | 耗时 |
|---|---|---|---|
| @Sendable 引用传递 | 100,000 项(sum=9,999,900,000) | verify=true | 1ms |
| 散列参数序列化 | 同上 | verify=true [plain copy] | 1ms |
本例数据量较小,两者差异不显著。当传递 MB 级
collections.Array时,@Sendable 的性能优势会显著体现。
使用 collections.Array<T> 传递大数组
如需跨线程传递大数组,使用 collections.Array 替代普通 Array:
import { collections, taskpool } from '@kit.ArkTS'
@Sendable
class BigData {
items: collections.Array<number> = new collections.Array<number>()
}
@Concurrent
function sumItems(data: BigData): number {
let total = 0
for (let i = 0; i < data.items.length; i++) {
total += data.items[i]
}
return total
}
// 填充 100 万项
const data = new BigData()
for (let i = 0; i < 1_000_000; i++) {
data.items.push(i)
}
// 引用传递,无需序列化 100 万项数据
const result = await taskpool.execute(new taskpool.Task(sumItems, data))
常见问题
Q:@Sendable 类能用 number[] 吗?
A:不能,编译器会报 arkts-sendable-prop-types 错误。必须改用 collections.Array<number>。这是 ArkTS 的静态类型安全保证,防止普通数组在多线程下被无保护地并发修改。
Q:@Sendable 对象在子线程中修改后,主线程能看到变化吗?
A:能看到引用传递的对象修改,但需要注意并发安全:多个线程同时写同一个字段会有数据竞争。建议子线程只读 @Sendable 对象,写操作回传给主线程处理。
Q:TaskPool 和 Worker 都支持 @Sendable 吗?
A:都支持。TaskPool 的 @Concurrent 函数参数可以是 @Sendable 对象;Worker 的 postMessage 同样支持传递 @Sendable 对象。
更多推荐


所有评论(0)