在传统 JS 引擎中,要优化对象的并发通信开销,唯一的方法是将实现下沉到 Native 侧,通过 Transferable 对象的转移或共享来降低开销。然而,仍有大量对象并发通信的需求,在 JS 引擎中尚未解决。ArkTS 提供了 Sendable 对象类型,它是一种可在 ArkTS 并发实例间安全共享和传递的数据类型,它支持引用传递来减少通信成本。Sendable对象可共享,跨线程前后指向同一个 JS 对象。如果Sendable对象通过调用 Napi 接口与一个 Native 对象绑定,当共享传递 Sendable 对象时,其绑定的 Native 对象也会一并共享传递。与其它 ArkTS 数据对象不同,符合 Sendable 协议的对象在运行时应为类型固定的对象。当多个并发实例尝试同时更新 Sendable 数据时,会发生数据竞争,如 ArkTS 共享容器的多线程操作。因此,ArkTS 提供异步锁机制来避免不同并发实例间的数据竞争,并提供了异步等机制来控制多线程处理数据的时序。同时,还可通过对象冻结接口将对象冻结为只读,从而避免数据竞争。Sendable 对象提供了并发实例间高效的通信能力,即引用传递,适用于自定义大对象需要线程间通信的场景,例如子线程读取数据库数据并返回给宿主线程。Sendable 对象Sendable 协议Sendable 协议定义了 ArkTS 的可共享对象体系及其规格约束,符合 Sendable 协议的数据 (简称 Sendable 数据) 可在 ArkTS 并发实例间传递。默认情况下,Sendable 数据在 ArkTS 并发实例间 (包括 UI 主线程、TaskPool 线程、Worker 线程) 采用引用传递方式,同时还支持拷贝传递方式。ISendable在 ArkTS 语言基础库 @arkts.lang 中引入了 interface ISendable,没有任何方法或属性。ISendable 是所有 Sendable类型 (除了 null 和undefined) 的父类型。ISendable 主要用于自定义 Sendable 数据结构的场景中。类装饰器 @Sendable 装饰器是 implement ISendable 的语法糖。从 API 11 开始,支持使用 @Sendable 装饰器校验 Sendable class从 API 12 开始,支持使用 @Sendable 装饰器校验 Sendable function,但对 API 12,需在工程中配置 “compatibleSdkVersionStage”: “beta3”,否则其 Sendable 特性将不生效;API 12 之后,可直接校验,无需进行其它配置从 API 22 开始,Sendable class 除了必须标注 @Sendable 装饰器,还可以叠加使用其它自定义装饰器Sendable 支持的数据类型ArkTS 基本数据类型:boolean、number、string、bigint、null、undefined,和常量枚举 (const eum)ArkTS 的容器类型 (须显式引入 @arkts.collections)ArkTS (须显式引入 @arkts.utils) 的异步锁对象、异步等待对象、SendableLruCache 对象继承了 ISendable 接口、标注了 @Sendable 的类或 function接入 Sendable 的系统对象:共享用户首选项 (@ohos.data.sendablePreferences)可共享的色彩管理 (@ohos.graphics.sendableColorSpaceManager)基于 Sendable 对象的图片处理 (@ohos.multimedia.sendableImage)资源管理 (@ohos.sendableResourceManager)Sendable 上下文管理 (@ohos.app.ability.sendableContextManager)元素均为 Sendable 类型的 union type 数据自定义的 Native Sendable 对象JS 内置对象、对象字面量和数组字面量在并发实例间传递时遵循结构化克隆算法,跨线程行为是拷贝传递。因此,其均不是 Sendable 类型Sendable的实现原理为了实现 Sendable 数据在不同并发实例间的引用传递,Sendable 共享对象分配在共享堆中,实现跨并发实例的内存共享。共享堆 (SharedHeap) 是进程级别的堆空间,与虚拟机本地堆 (LocalHeap) 不同,LocalHeap 仅限单个并发实例访问,而 SharedHeap 可被所有线程访问。Sendable 对象的跨线程行为为引用传递,因此,一个Sendable对象可能被多个并发实例引用。判断该Sendable对象是否存活,取决于所有并发实例是否存在对此 Sendable 对象的引用。各个并发实例的 LocalHeap 是隔离的,SharedHeap是进程级别的堆,可以被所有并发实例共享,但 SharedHeap 不能引用 LocalHeap 中的对象。Sendable 对象冻结Sendable 对象支持冻结操作,可通过调用Object.freeze接口冻结对象,冻结后,对象变为只读,不能修改属性。因此,多个并发实例间访问时无需加锁。不支持在 .ets 文件中使用 Object.freeze 接口,需在 ts 文件中封装 Object.freeze 方法【使用示例】提供 ts 文件封装 Object.freeze 方法exportfunctionfreezeObj(obj:any){Object.freeze(obj);}调用 freeze 方法冻结对象,然后将其发送到子线程import{freezeObj}from'./helper';import{worker}from'@kit.ArkTS';@SendableexportclassGlobalConfig{// 一些配置属性与方法init(){// 初始化相关逻辑freezeObj(this);// 初始化完成后冻结当前对象}}@Entry@Componentstruct Index{@Statemessage:string='Sendable freezeObj Test';build(){Column(){Text(this.message).onClick(()={letgConfig=newGlobalConfig();gConfig.init();constworkerInstance=newworker.ThreadWorker('entry/ets/workers/Worker.ets',{name:'Worker1'});workerInstance.postMessage(gConfig);this.message='success';})}.width('100%')}}子线程直接操作对象,不加锁import{ErrorEvent,MessageEvents,ThreadWorkerGlobalScope,worker}from'@kit.ArkTS';import{GlobalConfig}from'../managers/SendableFreeze';constworkerPort:ThreadWorkerGlobalScope=worker.workerPort;workerPort.onmessage=(e:MessageEvents)={letgConfig:GlobalConfig=e.data;// 使用 gConfig 对象,但不可修改属性}@Sendable 装饰器@Sendable说明装饰器参数无使用场景限制仅支持在Stage模型的.ets文件中使用装饰的函数类型限制仅支持装饰普通 function 和 Async function装饰的类继承关系限制Sendable class 只能继承 Sendable class,普通 Class 不可继承 Sendable class装饰的对象内的属性类型限制1. 支持 string、number、boolean、bigint、null、undefined、const enum、Sendable class、collections 容器集、ArkTSUtils.locks.AsyncLock、ArkTSUtils.SendableLruCache、ArkTSUtils.locks.ConditionVariable 以及自定义的 Sendable 函数类型;2. 禁止使用闭包变量,定义在顶层的 Sendable class 和 Sendable function 除外;3. 不支持计算属性和类型别名;4. 私有需用 private 定义,而不能使用 #装饰的对象内的属性的其它限制1. 成员属性必须显式初始化,不能使用感叹号2. 不支持增加或删除属性,允许修改属性,修改前后属性的类型必须一致,不支持修改方法装饰的函数或类对象内的方法参数限制允许使用 local 变量、入参和通过 import 引入的变量。禁止使用闭包变量,但定义在顶层的 Sendable class 和 Sendable function 除外。从API version 18开始,支持访问本文件导出的变量适用场景1. 在 TaskPool 或 Worker 中使用类方法或 Sendable 函数2. 传输对象数据量较大的场景,序列化耗时会随着数据量增大而增大,使用S endable 对数据进行改造后,可提高传输效率@Sendable 装饰的类,所有成员属性必须显示初始化,且不能增删属性,不能更改属性类型,但属性值可修改【示例】@SendabletypeSendableFuncType=()=void;@SendableclassSendableClass{num:number=1;}@SendablefunctionSendableFunction(){console.info('Top level sendable function');}Sendable 使用场景Sendable 对象在不同并发实例间默认采用引用传递,这种方式比序列化更高效,且不会丢失类成员方法。因此,Sendable 能够解决两个关键场景的问题:跨并发实例传输大数据 (例如达到100KB以上的数据)由于跨并发实例序列化的开销随数据量线性增长,因此当传输数据量较大时 (100KB的数据传输耗时约为1ms),跨并发实例的拷贝开销会影响应用性能,使用引用传递方式传输对象可提升性能。跨并发实例传递带方法的 class 实例对象在序列化传输实例对象时,会丢失方法,若需调用实例方法,应使用引用传递。处理数据时,若需解析数据,可使用 ASON 工具。【示例】// 定义模拟类 Test,定义了实例方法import{lang,collections}from'@kit.ArkTS'exporttypeISendable=lang.ISendable;@SendableexportclassSendableTestClass{publicname:string='John';publicage:number=20;publicarr:collections.Arraynumber=newcollections.Arraynumber(1,2,3);setAge(age:number):void{this.age=age;}getName():string{returnthis.name;}getAge():number{returnthis.age;}}import{taskpool,ArkTSUtils}from'@kit.ArkTS';import{SendableTestClass,ISendable}from'./sendable';// 在并发函数中模拟数据处理
ArkTS -- @Sendable (通信对象)
在传统 JS 引擎中,要优化对象的并发通信开销,唯一的方法是将实现下沉到 Native 侧,通过 Transferable 对象的转移或共享来降低开销。然而,仍有大量对象并发通信的需求,在 JS 引擎中尚未解决。ArkTS 提供了 Sendable 对象类型,它是一种可在 ArkTS 并发实例间安全共享和传递的数据类型,它支持引用传递来减少通信成本。Sendable对象可共享,跨线程前后指向同一个 JS 对象。如果Sendable对象通过调用 Napi 接口与一个 Native 对象绑定,当共享传递 Sendable 对象时,其绑定的 Native 对象也会一并共享传递。与其它 ArkTS 数据对象不同,符合 Sendable 协议的对象在运行时应为类型固定的对象。当多个并发实例尝试同时更新 Sendable 数据时,会发生数据竞争,如 ArkTS 共享容器的多线程操作。因此,ArkTS 提供异步锁机制来避免不同并发实例间的数据竞争,并提供了异步等机制来控制多线程处理数据的时序。同时,还可通过对象冻结接口将对象冻结为只读,从而避免数据竞争。Sendable 对象提供了并发实例间高效的通信能力,即引用传递,适用于自定义大对象需要线程间通信的场景,例如子线程读取数据库数据并返回给宿主线程。Sendable 对象Sendable 协议Sendable 协议定义了 ArkTS 的可共享对象体系及其规格约束,符合 Sendable 协议的数据 (简称 Sendable 数据) 可在 ArkTS 并发实例间传递。默认情况下,Sendable 数据在 ArkTS 并发实例间 (包括 UI 主线程、TaskPool 线程、Worker 线程) 采用引用传递方式,同时还支持拷贝传递方式。ISendable在 ArkTS 语言基础库 @arkts.lang 中引入了 interface ISendable,没有任何方法或属性。ISendable 是所有 Sendable类型 (除了 null 和undefined) 的父类型。ISendable 主要用于自定义 Sendable 数据结构的场景中。类装饰器 @Sendable 装饰器是 implement ISendable 的语法糖。从 API 11 开始,支持使用 @Sendable 装饰器校验 Sendable class从 API 12 开始,支持使用 @Sendable 装饰器校验 Sendable function,但对 API 12,需在工程中配置 “compatibleSdkVersionStage”: “beta3”,否则其 Sendable 特性将不生效;API 12 之后,可直接校验,无需进行其它配置从 API 22 开始,Sendable class 除了必须标注 @Sendable 装饰器,还可以叠加使用其它自定义装饰器Sendable 支持的数据类型ArkTS 基本数据类型:boolean、number、string、bigint、null、undefined,和常量枚举 (const eum)ArkTS 的容器类型 (须显式引入 @arkts.collections)ArkTS (须显式引入 @arkts.utils) 的异步锁对象、异步等待对象、SendableLruCache 对象继承了 ISendable 接口、标注了 @Sendable 的类或 function接入 Sendable 的系统对象:共享用户首选项 (@ohos.data.sendablePreferences)可共享的色彩管理 (@ohos.graphics.sendableColorSpaceManager)基于 Sendable 对象的图片处理 (@ohos.multimedia.sendableImage)资源管理 (@ohos.sendableResourceManager)Sendable 上下文管理 (@ohos.app.ability.sendableContextManager)元素均为 Sendable 类型的 union type 数据自定义的 Native Sendable 对象JS 内置对象、对象字面量和数组字面量在并发实例间传递时遵循结构化克隆算法,跨线程行为是拷贝传递。因此,其均不是 Sendable 类型Sendable的实现原理为了实现 Sendable 数据在不同并发实例间的引用传递,Sendable 共享对象分配在共享堆中,实现跨并发实例的内存共享。共享堆 (SharedHeap) 是进程级别的堆空间,与虚拟机本地堆 (LocalHeap) 不同,LocalHeap 仅限单个并发实例访问,而 SharedHeap 可被所有线程访问。Sendable 对象的跨线程行为为引用传递,因此,一个Sendable对象可能被多个并发实例引用。判断该Sendable对象是否存活,取决于所有并发实例是否存在对此 Sendable 对象的引用。各个并发实例的 LocalHeap 是隔离的,SharedHeap是进程级别的堆,可以被所有并发实例共享,但 SharedHeap 不能引用 LocalHeap 中的对象。Sendable 对象冻结Sendable 对象支持冻结操作,可通过调用Object.freeze接口冻结对象,冻结后,对象变为只读,不能修改属性。因此,多个并发实例间访问时无需加锁。不支持在 .ets 文件中使用 Object.freeze 接口,需在 ts 文件中封装 Object.freeze 方法【使用示例】提供 ts 文件封装 Object.freeze 方法exportfunctionfreezeObj(obj:any){Object.freeze(obj);}调用 freeze 方法冻结对象,然后将其发送到子线程import{freezeObj}from'./helper';import{worker}from'@kit.ArkTS';@SendableexportclassGlobalConfig{// 一些配置属性与方法init(){// 初始化相关逻辑freezeObj(this);// 初始化完成后冻结当前对象}}@Entry@Componentstruct Index{@Statemessage:string='Sendable freezeObj Test';build(){Column(){Text(this.message).onClick(()={letgConfig=newGlobalConfig();gConfig.init();constworkerInstance=newworker.ThreadWorker('entry/ets/workers/Worker.ets',{name:'Worker1'});workerInstance.postMessage(gConfig);this.message='success';})}.width('100%')}}子线程直接操作对象,不加锁import{ErrorEvent,MessageEvents,ThreadWorkerGlobalScope,worker}from'@kit.ArkTS';import{GlobalConfig}from'../managers/SendableFreeze';constworkerPort:ThreadWorkerGlobalScope=worker.workerPort;workerPort.onmessage=(e:MessageEvents)={letgConfig:GlobalConfig=e.data;// 使用 gConfig 对象,但不可修改属性}@Sendable 装饰器@Sendable说明装饰器参数无使用场景限制仅支持在Stage模型的.ets文件中使用装饰的函数类型限制仅支持装饰普通 function 和 Async function装饰的类继承关系限制Sendable class 只能继承 Sendable class,普通 Class 不可继承 Sendable class装饰的对象内的属性类型限制1. 支持 string、number、boolean、bigint、null、undefined、const enum、Sendable class、collections 容器集、ArkTSUtils.locks.AsyncLock、ArkTSUtils.SendableLruCache、ArkTSUtils.locks.ConditionVariable 以及自定义的 Sendable 函数类型;2. 禁止使用闭包变量,定义在顶层的 Sendable class 和 Sendable function 除外;3. 不支持计算属性和类型别名;4. 私有需用 private 定义,而不能使用 #装饰的对象内的属性的其它限制1. 成员属性必须显式初始化,不能使用感叹号2. 不支持增加或删除属性,允许修改属性,修改前后属性的类型必须一致,不支持修改方法装饰的函数或类对象内的方法参数限制允许使用 local 变量、入参和通过 import 引入的变量。禁止使用闭包变量,但定义在顶层的 Sendable class 和 Sendable function 除外。从API version 18开始,支持访问本文件导出的变量适用场景1. 在 TaskPool 或 Worker 中使用类方法或 Sendable 函数2. 传输对象数据量较大的场景,序列化耗时会随着数据量增大而增大,使用S endable 对数据进行改造后,可提高传输效率@Sendable 装饰的类,所有成员属性必须显示初始化,且不能增删属性,不能更改属性类型,但属性值可修改【示例】@SendabletypeSendableFuncType=()=void;@SendableclassSendableClass{num:number=1;}@SendablefunctionSendableFunction(){console.info('Top level sendable function');}Sendable 使用场景Sendable 对象在不同并发实例间默认采用引用传递,这种方式比序列化更高效,且不会丢失类成员方法。因此,Sendable 能够解决两个关键场景的问题:跨并发实例传输大数据 (例如达到100KB以上的数据)由于跨并发实例序列化的开销随数据量线性增长,因此当传输数据量较大时 (100KB的数据传输耗时约为1ms),跨并发实例的拷贝开销会影响应用性能,使用引用传递方式传输对象可提升性能。跨并发实例传递带方法的 class 实例对象在序列化传输实例对象时,会丢失方法,若需调用实例方法,应使用引用传递。处理数据时,若需解析数据,可使用 ASON 工具。【示例】// 定义模拟类 Test,定义了实例方法import{lang,collections}from'@kit.ArkTS'exporttypeISendable=lang.ISendable;@SendableexportclassSendableTestClass{publicname:string='John';publicage:number=20;publicarr:collections.Arraynumber=newcollections.Arraynumber(1,2,3);setAge(age:number):void{this.age=age;}getName():string{returnthis.name;}getAge():number{returnthis.age;}}import{taskpool,ArkTSUtils}from'@kit.ArkTS';import{SendableTestClass,ISendable}from'./sendable';// 在并发函数中模拟数据处理