Sendable装饰器的使用
Sendable对象是符合ArkTS语言规范的可共享对象,需通过@Sendable装饰器标记,并且满足Sendable约束。
@Sendable装饰器
装饰class、function
Sendable使用规则与约束
-
Sendable类必须继承自Sendable类
-
非Sendable类禁止实现Sendable接口
import { lang } from '@kit.ArkTS';type ISendable = lang.ISendable;interface I extends ISendable {};class B implements I {}; // I是sendable interface,B不能实现,编译报错
-
Sendable类/接口成员变量:不支持使用!断言、不支持使用计算属性名
计算属性名
- 普通属性名(固定属性名)
const obj = {name: "张三", // 固定属性名 "name"age: 25 // 固定属性名 "age" };
- 计算属性名(动态属性名)
const propName = "userName"; const dynamicKey = "Age";const obj = {[propName]: "李四", // 计算属性名,值为 "userName"["user" + dynamicKey]: 30, // 计算属性名,值为 "userAge"["total" + "Score"]: 100 // 计算属性名,值为 "totalScore" };
-
泛型类中的Sendable类、SendableLruCache、collections.Array、collections.Map和collections.Set的模板类型必须是Sendable类型
-
Sendable类的内部不允许使用当前模块内上下文环境中定义的变量
Sendable类内部只能访问:
- top level 的 Sendable class 对象(类本身,不是实例)
- 静态成员(static 成员)
- 其他top level的Sendable类
-
@Sendable装饰器仅支持修饰类和函数
-
禁止使用对象字面量 数组字面量初始化Sendable对象
import { collections } from '@kit.ArkTS';let arr2: collections.Array<number> = [1, 2, 3]; // 不是Sendable类型,编译报错 let arr3: number[] = [1, 2, 3]; // 不是Sendable类型,正例,不报错 let arr4: number[] = new collections.Array<number>(1, 2, 3); // 编译报错
-
箭头函数不可标记为Sendable
@Sendable type SendableFuncType = () => void; let func: SendableFuncType = () => {}; // 编译报错@Sendable class SendableClass {func: SendableFuncType = () => {}; // 编译报错 }
异步锁
为了防止@Sendable共享对象在不同线程中修改共享变量导致的竞争问题,可以使用异步锁保护数据。
import { ArkTSUtils, taskpool } from '@kit.ArkTS';@Sendable
export class A {private count_: number = 0;lock_: ArkTSUtils.locks.AsyncLock = new ArkTSUtils.locks.AsyncLock();public getCount(): Promise<number> {// 对需要保护的数据加异步锁return this.lock_.lockAsync(() => {return this.count_;})}public async increaseCount() {// 对需要保护的数据加异步锁await this.lock_.lockAsync(() => {this.count_++;})}
}@Concurrent
async function printCount(a: A) {a.increaseCount();console.info("InputModule: count is:" + await a.getCount());
}@Entry
@Component
struct Index {@State message: string = 'Hello World';build() {RelativeContainer() {Text(this.message).id('HelloWorld').fontSize(50).fontWeight(FontWeight.Bold).alignRules({center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(async () => {// 创建sendable对象alet a: A = new A();// 将实例a传递给子线程await taskpool.execute(printCount, a);})}.height('100%').width('100%')}
}
异步等待
ArkTS引入了异步任务的等待和被唤醒能力,以解决多线程任务时序控制问题。
import { ArkTSUtils, taskpool } from '@kit.ArkTS';//通知所有等待线程
@Concurrent
function notifyAll(conditionVariable: ArkTSUtils.locks.ConditionVariable) {conditionVariable.notifyAll();
}//通知一个等待线程
@Concurrent
function notifyOne(conditionVariable: ArkTSUtils.locks.ConditionVariable) {conditionVariable.notifyOne();
}//无限等待
@Concurrent
async function wait(conditionVariable: ArkTSUtils.locks.ConditionVariable) {await conditionVariable.wait().then(() => {console.info(`TaskPool Thread Wait: success`);});
}//超时等待
@Concurrent
async function waitFor(conditionVariable: ArkTSUtils.locks.ConditionVariable) {await conditionVariable.waitFor(3000).then(() => {console.info(`TaskPool Thread WaitFor: success`);});
}@Entry
@Component
struct Index {@State message: string = 'Hello World';build() {Row() {Column() {Text(this.message).fontSize(50).fontWeight(FontWeight.Bold).onClick(() => {// 1. 方式1:匿名条件变量// 创建conditionVariable对象const conditionVariable: ArkTSUtils.locks.ConditionVariable = new ArkTSUtils.locks.ConditionVariable();// 将实例conditionVariable传递给wait线程taskpool.execute(wait, conditionVariable);// 将实例conditionVariable传递给notifyAll线程,唤醒wait线程,日志输出"TaskPool Thread Wait: success"taskpool.execute(notifyAll, conditionVariable);// 将实例conditionVariable传递给waitFor线程taskpool.execute(waitFor, conditionVariable);// 将实例conditionVariable传递给notifyOne线程,唤醒waitFor线程,日志输出"TaskPool Thread WaitFor: success"taskpool.execute(notifyOne, conditionVariable);// 2. 方式2:命名条件变量// 创建有name的conditionVariable对象(可以在不同地方通过名称获取同一实例)const conditionVariableRequest: ArkTSUtils.locks.ConditionVariable =ArkTSUtils.locks.ConditionVariable.request("Request1");// 将实例conditionVariableRequest传递给wait线程taskpool.execute(wait, conditionVariableRequest);// 将实例conditionVariableRequest传递给notifyAll线程,唤醒wait线程,日志输出"TaskPool Thread Wait: success"taskpool.execute(notifyAll, conditionVariableRequest);// 将实例conditionVariableRequest传递给waitFor线程taskpool.execute(waitFor, conditionVariableRequest);// 将实例conditionVariableRequest传递给notifyOne线程,唤醒waitFor线程,日志输出"TaskPool Thread WaitFor: success"taskpool.execute(notifyOne, conditionVariableRequest);})}.width('100%')}.height('100%')}
}
ASON解析与生成
ASON工具与JS提供的JSON工具类似,JSON用于进行JS对象的序列化(stringify)、反序列化(parse)。ASON则提供了Sendable对象的序列化、反序列化**能力。
共享容器
ArkTS共享容器在多个并发实例间传递时,默认采用引用传递,允许多个并发实例操作同一容器实例。此外,还支持拷贝传递,即每个并发实例拥有独立的ArkTS容器实例。
ArkTS共享容器不是线程安全的,内部使用了fail-fast(快速失败)机制,即当检测到多个并发实例同时对容器进行结构性修改时,会触发异常。因此,在多线程场景下修改容器内属性时,开发者需要使用ArkTS提供的异步锁机制保证ArkTS容器的安全访问。
ArkTS共享容器包含如下几种:Array、Map、Set、TypedArray(Int8Array、Uint8Array、Int16Array、Uint16Array、Int32Array、Uint32Array、Uint8ClampedArray、Float32Array)、ArrayBuffer、BitVector、ConcatArray。
import { ArkTSUtils, collections, taskpool } from '@kit.ArkTS';@Concurrent
async function add(arr: collections.Array<number>, lock: ArkTSUtils.locks.AsyncLock) {await lock.lockAsync(() => { // 如果不添加异步锁,任务会因为数据竞争冲突,导致抛异常失败arr[0]++;})
}@Entry
@Component
struct Index {@State message: string = 'Hello World';build() {RelativeContainer() {Text(this.message).id('HelloWorld').fontSize(50).fontWeight(FontWeight.Bold).alignRules({center: { anchor: '__container__', align: VerticalAlign.Center },middle: { anchor: '__container__', align: HorizontalAlign.Center }}).onClick(() => {let taskGroup = new taskpool.TaskGroup();let lock = new ArkTSUtils.locks.AsyncLock();let arr = collections.Array.create<number>(1, 0);let count = 1000;let num = count;while (num--) {taskGroup.addTask(add, arr, lock);}taskpool.execute(taskGroup).then(() => {console.info(`Return success: ${arr[0]} === ${count}`);}).catch((e: Error) => {console.error("Return error.");})})}.height('100%').width('100%')}
}
共享模块
共享模块是进程内只会加载一次的模块,使用"use shared"这一指令来标记一个模块是否为共享模块。
非共享模块在同一线程内只加载一次,而在不同线程中会多次加载,每个线程都会生成新的模块对象。因此,目前只能使用共享模块实现进程单例。
-
"use shared"需要与"use strict"一样写在ArkTS文件顶层,写在import语句之后其他语句之前。
-
共享属性不具备传递性。非共享模块A即使引入了共享模块B,也不会因此变成共享模块。
-
共享模块只支持ets文件。
-
共享模块内不允许使用side-effects-import。
// 不允许使用side-effects-import,编译报错 import "./test"; "use shared"
-
共享模块在同一进程内仅加载一次,可在不同线程间共享。
-
共享模块加载时,导入的非共享模块不会立即加载。在共享模块内访问依赖的非共享模块导出变量时,当前线程会懒加载对应的非共享模块。非共享模块在线程间隔离,不同线程访问时会进行一次懒加载。
-
由于side-effects-import不涉及导出变量,因此不会被加载,也不受支持。
-
共享模块导出的变量必须是可共享对象。
-
共享模块在并发实例间可共享,因此导出的所有对象必须是可共享的。
-
共享模块不支持re-export写法。
-
共享模块可以引用其他共享模块或非共享模块,引用和被引用场景没有限制。
-
仅支持使用静态加载、napi_load_module或napi_load_module_with_info加载共享模块。
Sendable对象冻结
Sendable对象支持冻结操作。冻结后,对象变为只读,不能修改属性。因此,多个并发实例间访问时无需加锁。可以通过调用Object.freeze接口冻结对象。
-
提供ts文件封装Object.freeze方法。
// helper.ts export function freezeObj(obj: any) {Object.freeze(obj); }
-
调用freeze方法冻结对象,然后将其发送到子线程。
// Index.ets import { freezeObj } from './helper'; import { worker } from '@kit.ArkTS';@Sendable export class GlobalConfig {// 一些配置属性与方法init() {// 初始化相关逻辑freezeObj(this); // 初始化完成后冻结当前对象} }@Entry @Component struct Index {build() {Column() {Text("Sendable freezeObj Test").id('HelloWorld').fontSize(50).fontWeight(FontWeight.Bold).onClick(() => {let gConfig = new GlobalConfig();gConfig.init();const workerInstance = new worker.ThreadWorker('entry/ets/workers/Worker.ets', { name: "Worker1" });workerInstance.postMessage(gConfig);})}.height('100%').width('100%')} }
-
子线程直接操作对象,不加锁。
// Worker.ets import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS'; import { GlobalConfig } from '../pages/Index';const workerPort: ThreadWorkerGlobalScope = worker.workerPort; workerPort.onmessage = (e: MessageEvents) => {let gConfig: GlobalConfig = e.data;// 使用gConfig对象 }