@Sendable 类 vs 普通 class:ArkTS 跨线程编程的正确姿势

前言

HarmonyOS 的 ArkTS 采用线程隔离模型:UI 主线程、TaskPool 工作线程、Worker 各自独立,普通的对象实例不能跨线程传递。很多开发者第一次用 taskpool.execute 把对象当参数传进去,直接报错"object is not sendable"。本文基于论坛问题「ArkTS 里 @Sendable 类和普通 class 在 ArkCompiler 有何不同」,讲清 @Sendable 的本质与约束。

问题描述

  • 把普通 class 实例作为参数传给 taskpool.execute / Worker,运行时报错;
  • 以为 @Sendable 是"引用共享",改了一边另一边也变,结果并没有;
  • @Sendable 类的方法里用了闭包/外部变量,编译直接报错;
  • 不清楚哪些字段类型可以用、哪些不行。

细节解析

1. 线程隔离是前提

ArkTS 不支持线程间共享内存。一个对象只在它被创建的线程里有效,跨线程必须序列化传递。普通 class 没有"可序列化"的编译期保证,因此禁止跨线程。

2. @Sendable:编译期保证"可跨线程"

@Sendable 装饰的类,编译器会强制约束其成员满足可序列化要求(字段类型必须是 Sendable 兼容类型、方法不能捕获外部变量、不能继承等)。满足后,它就可以作为参数传给 TaskPool/Worker,传递时按拷贝到目标线程。

3. 值语义,不是引用共享

这是最容易误解的点:@Sendable 实例跨线程是拷贝,目标线程改了它,原线程的那份不会变。如果需要真正共享状态,要用 SharedArrayBuffer、或借助分布式/共享存储,而不是指望对象本身共享。

4. 约束清单

  • 字段只能是用 Sendable 兼容类型(number/string/boolean/@Sendable class/Array 等);
  • 不能有非 Sendable 实例字段;
  • 方法不能引用外层作用域变量(即不能有闭包);
  • 不支持继承(extends 受限);
  • 构造函数需给字段赋初值。

示例代码

// 可跨线程的点
@Sendable
class Point {
  x: number = 0;
  y: number = 0;
  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }
  move(dx: number, dy: number): void {
    this.x += dx;   // 注意:只改当前线程的副本
    this.y += dy;
  }
}

import { taskpool } from '@kit.ArkTS';
@Concurrent
function shiftPoint(p: Point): Point {
  p.move(10, 10);
  return p;   // 返回的是目标线程里的副本
}

// 使用
const p = new Point(0, 0);
taskpool.execute(shiftPoint, p).then((res: Point) => {
  console.info('worker 里变成:', res.x, res.y); // 10, 10
  console.info('主线程原对象仍是:', p.x, p.y);   // 0, 0(未被共享修改)
});

总结

  • 普通 class 实例不能跨线程;@Sendable class 可以,前提是满足编译期约束;
  • @Sendable值拷贝语义,跨线程后两端各自独立,改一边不影响另一边;
  • 方法不能有闭包、字段必须 Sendable 兼容、不支持继承;
  • 真正需要共享状态请用 SharedArrayBuffer 或外部存储,而非依赖对象引用。

理解"隔离 + 值拷贝",就不会再被 not sendable 和"改了不生效"困扰。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐