把批量文件解析、图片压缩、大数据排序扔进 TaskPool,是性能优化里最顺手的动作。你在主线程 new 了一个业务 Bean,字段干干净净,类上也老老实实加了 @Sendable,taskpool.execute(task, bean) 一跑,直接抛 Object is not Sendable。你翻来覆去看这个类,确实只有 name、age、tags,每个字段看起来都“能序列化”。

问题不在你标记的顶层类,在对象图。@Sendable 不是给类盖一个章就自动把成员变成可传输类型,运行时会把整个引用链递归检查一遍。只要嵌套的某一级是普通对象字面量、普通 Array/Map/Set、函数、Context、资源对象或者 Native 句柄,整个 Bean 都会被判定为不可传输。

JSON.parse 出来的不是 Sendable 实例

最隐蔽的一种失败:类定义了 @Sendable,但实例是从接口响应 JSON.parse(text) as FileJob 得到的。类型断言只骗编译器,不执行构造函数,拿到的仍然是普通对象。{ ...bean } 和 Object.assign({}, bean) 也一样,拷贝出来的是普通对象字面量,不是 @Sendable 类的新实例。跨线程传过去,运行时该拒绝照样拒绝。

如果确实需要带方法的类实例跨线程,API 11 起可以走 @Sendable,但每个字段也必须满足 Sendable 约束。集合字段不能再用普通 Array,要换成 @kit.ArkTS 的 collections.Array、collections.Map、collections.Set;嵌套的业务类也要递归标记 @Sendable。

typescript

import { collections } from '@kit.ArkTS'

@Sendable
class ParseJob {
  filePath: string = ''
  offset: number = 0
  tags: collections.Array<string> = new collections.Array()
}

@Concurrent
function parseChunk(job: ParseJob): number {
  // 只做纯计算,不碰 UI、不碰 Context
  return job.tags.length
}

collections.Array 这一行是关键。你写普通 string[],编译期可能不报,运行时整个对象图检查到这一层就断了。另一个常见错误是往 @Sendable 类里塞 @State 装饰器包装的对象、RDB Store 或已打开的文件句柄,这些留在主线程,子线程只该拿文件路径和偏移量。

TaskPool 和 Worker 的通信接口不是一回事

postMessage 是 Worker 的接口,TaskPool 的任务向宿主回传中间数据用的是 taskpool.Task.sendData(),宿主侧用 task.onReceiveData() 接。接口调错,有时候不报“序列化失败”,而是数据静默不到,你以为子线程没跑完,其实结果发到了错误的通道上。

最稳的边界是线程间只传不可变 DTO,线程内自行创建工作对象。大数据优先传 ArrayBuffer,业务方法和复杂对象回到主线程再重建。@Sendable 能省掉一次拷贝,但它不是“随便传”的许可证;对象图里有一环不干净,整条链路就断。

Logo

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

更多推荐