非共享模塊在同一線程內只加載一次,在不同線程間會加載多次,單例類也會創建多次,導致數據不共享,在不同的線程內都會產生新的模塊對象。
基礎概念
Sendable協議
Sendable協議定義了ArkTS的可共享對象體系及其規格約束。符合Sendable協議的數據(以下簡稱Sendable數據)可以在ArkTS并發實例間傳遞。
默認情況下,Sendable數據在ArkTS并發實例間(包括主線程、TaskPool&Worker工作線程)傳遞的行為是引用傳遞。同時,ArkTS支持Sendable數據在ArkTS并發實例間的拷貝傳遞。
當多個并發實例嘗試同時更新可變Sendable數據時,會發生數據競爭。ArkTS提供了異步鎖的機制來避免不同并發實例間的數據競爭。
示例:
import { taskpool, worker } from '@kit.ArkTS';@Sendable
class A {}let a: A = new A();@Concurrent
function foo(a: A) {}
let task: taskpool.Task = new taskpool.Task(foo, a)let w = new worker.ThreadWorker("entry/ets/workers/Worker.ets")// 1. TaskPool 共享傳輸實現方式
taskpool.execute(task).then(() => {})// 2. Worker 共享傳輸實現方式
w.postMessageWithSharedSendable(a)// 3. TaskPool 拷貝傳輸實現方式
task.setCloneList([a])
taskpool.execute(task).then(() => {})// 4. Worker 拷貝傳輸實現方式
w.postMessage(a)
Sendable class
Sendable class需同時滿足以下兩個規則:
- 當且僅當被標注了@Sendable裝飾器。
- 需滿足Sendable約束,詳情可查Sendable使用規則。
Sendable interface
Sendable interface需同時滿足以下兩個規則:
- 當且僅當是ISendable或者繼承了ISendable。
- 需滿足Sendable約束,詳情可查Sendable使用規則。
Sendable支持的數據類型
- 所有的ArkTS基本數據類型:boolean, number, string, bigint, null, undefined。
- ArkTS語言標準庫中定義的容器類型數據(須顯式引入@arkts.collections)。
- ArkTS語言標準庫中定義的AsyncLock對象(須顯式引入@arkts.utils)。
- 繼承了ISendable的interface。
- 標注了@Sendable裝飾器的class。
- 接入Sendable的系統對象類型(詳見Sendable系統對象)。
- 元素均為Sendable類型的union type數據。
說明:
JS內置對象在并發實例間的傳遞遵循結構化克隆算法,語義為拷貝傳遞。因此JS內置對象的實例不是Sendable類型。
對象字面量、數組字面量在并發實例間的傳遞遵循結構化克隆算法,語義為拷貝傳遞。因此,對象字面量和數組字面量不是Sendable類型。
ArkTS容器集與原生API行為差異具體參考行為差異匯總。
ISendable
在ArkTS語言基礎庫@arkts.lang中引入interface ISendable {},沒有任何必須的方法或屬性。ISendable是所有Sendable類型(除了null和undefined)的父類型。ISendable主要用在開發者自定義Sendable數據結構的場景中。類裝飾器@Sendable是implement ISendable的語法糖。
@Sendable裝飾器:聲明并校驗Sendable class
說明:
從API version 11開始,該裝飾器支持在ArkTS卡片中使用。
裝飾器說明
@Sendable類裝飾器 | 說明 |
---|---|
裝飾器參數 | 無。 |
使用場景限制 | 僅支持在Stage模型的工程中使用。僅支持在.ets文件中使用。 |
裝飾的類繼承關系限制 | Sendable class只能繼承Sendable class,普通Class不可以繼承Sendable class。 |
裝飾的對象內的屬性類型限制 | 1. 支持string、number、boolean、bigint、null、undefined、Sendable class、collections.Array、collections.Map、collections.Set。 2. 禁止使用閉包變量。 3. 不支持#定義私有屬性,需用private。 4. 不支持計算屬性。 |
裝飾的對象內的屬性的其他限制 | 成員屬性必須顯式初始化。成員屬性不能跟感嘆號。 |
裝飾的對象內的方法參數限制 | 允許使用local變量、入參和通過import引入的變量。禁止使用閉包變量。 |
Sendable Class的限制 | 不支持增加屬性、不支持刪除屬性、允許修改屬性,修改前后屬性的類型必須一致、不支持修改方法。 |
適用場景 | 1. 在TaskPool或Worker中使用類方法。 2. 傳輸對象數據量較大的使用場景。 |
裝飾器使用示例
@Sendable
class SendableTestClass {desc: string = "sendable: this is SendableTestClass ";num: number = 5;printName() {console.info("sendable: SendableTestClass desc is: " + this.desc);}get getNum(): number {return this.num;}
}
Sendable使用規則
1. Sendable class只能繼承自Sendable class
說明:
這里的class不包括變量。Sendable class不能繼承自變量。
正例:
@Sendable
class A {constructor() {}
}@Sendable
class B extends A {constructor() {super()}
}
反例:
class A {constructor() {}
}@Sendable
class B extends A {constructor() {super()}
}
2. 非Sendable class只能繼承自非Sendable class
正例:
class A {constructor() {}
}class B extends A {constructor() {super()}
}
反例:
@Sendable
class A {constructor() {}
}class B extends A {constructor() {super()}
}
3. 非Sendable class只能實現非Sendable interface
正例:
interface I {};class B implements I {};
反例:
import { lang } from '@kit.ArkTS';type ISendable = lang.ISendable;interface I extends ISendable {};class B implements I {};
4. Sendable class/interface成員變量必須是Sendable支持的數據類型
正例:
@Sendable
class A {constructor() {}a: number = 0;
}
反例:
@Sendable
class A {constructor() {}b: Array<number> = [1, 2, 3] // 需使用collections.Array
}
5. Sendable class/interface的成員變量不支持使用!斷言
正例:
@Sendable
class A {constructor() {}a: number = 0;
}
反例:
@Sendable
class A {constructor() {}a!: number;
}
6. Sendable class/interface的成員變量不支持使用計算屬性名
正例:
@Sendable
class A {num1: number = 1;num2: number = 2;add(): number {return this.num1 + this.num2;}
}
反例:
enum B {b1 = "bbb"
}
@Sendable
class A {["aaa"]: number = 1; // ["aaa"] is allowed in other classes in ets files[B.b1]: number = 2; // [B.b1] is allowed in other classes in ets files
}
7. 泛型類中的Sendable class,collections.Array,collections.Map,collections.Set的模板類型必須是Sendable類型
正例:
import { collections } from '@kit.ArkTS';try {let arr1: collections.Array<number> = new collections.Array<number>();let num: number = 1;arr1.push(num)
} catch (e) {console.error(`taskpool execute: Code: ${e.code}, message: ${e.message}`);
}
反例:
import { collections } from '@kit.ArkTS';try {let arr1: collections.Array<Array<number>> = new collections.Array<Array<number>>();let arr2: Array<number> = new Array<number>()arr2.push(1)arr1.push(arr2)
} catch (e) {console.error(`taskpool execute: Code: ${e.code}, message: ${e.message}`);
}
8. Sendable class的內部不允許使用當前模塊內上下文環境中定義的變量
由于Sendable對象在不同并發實例間的上下文環境不同,如果直接訪問會有非預期行為。不支持Sendable對象使用當前模塊內上下文環境中定義的變量,如果違反,編譯階段會報錯。
說明:
從API version 12開始,sendable class的內部支持使用top level的sendable class對象。
正例:
import { lang } from '@kit.ArkTS';type ISendable = lang.ISendable;interface I extends ISendable {}@Sendable
class B implements I {static o: number = 1;static bar(): B {return new B();}
}@Sendable
class C {v: I = new B();u: number = B.o;foo() {return B.bar();}
}
反例:
import { lang } from '@kit.ArkTS';type ISendable = lang.ISendable;interface I extends ISendable {}@Sendable
class B implements I {}function bar(): B {return new B();
}let b = new B();{@Sendableclass A implements I {}@Sendableclass C {u: I = bar(); // bar不是sendable class對象,編譯報錯v: I = new A(); // A不是定義在top level中,編譯報錯foo() {return b; // b不是sendable class對象,而是sendable class的實例,編譯報錯}}
}
9. Sendable class中不能使用除了@Sendable的其它裝飾器
如果類裝飾器定義在ts文件中,產生修改類的布局的行為,那么會造成運行時的錯誤。
正例:
@Sendable
class A {num: number = 1;
}
反例:
@Sendable
@Observed
class C {num: number = 1;
}
10. 不能使用對象字面量/數組字面量初始化Sendable類型
Sendable數據類型只能通過Sendable類型的new表達式創建。
正例:
import { collections } from '@kit.ArkTS';let arr1: collections.Array<number> = new collections.Array<number>(1, 2, 3); // 是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); // 編譯報錯
11. 非Sendable類型不可以as成Sendable類型
說明:
Sendable類型在不違反Sendable規則的前提下需要和非Sendable類型行為兼容,因此Sendable類型可以as成非Sendable類型。
正例:
class A {state: number = 0;
}@Sendable
class SendableA {state: number = 0;
}let a1: A = new SendableA() as A;
反例:
class A {state: number = 0;
}@Sendable
class SendableA {state: number = 0;
}let a2: SendableA = new A() as SendableA;
與TS/JS交互的規則
ArkTS通用規則(目前只針對Sendable對象)
規則 |
---|
Sendable對象傳入TS/JS的接口中,禁止操作其對象布局(增、刪屬性,改變屬性類型)。 |
Sendable對象設置到TS/JS的對象上,TS中獲取到這個Sendable對象后,禁止操作其對象布局(增、刪屬性,改變屬性類型)。 |
Sendable對象放入TS/JS的容器中,TS中獲取到這個Sendable對象后,禁止操作其對象布局(增、刪屬性,改變屬性類型)。 |
說明:
此處改變屬性類型不包括Sendable對象類型的改變,比如從Sendable class A 變為Sendable class B。
NAPI規則(目前只針對Sendable對象)
規則 |
---|
禁止刪除屬性,不能使用的接口有:napi_delete_property。 |
禁止新增屬性,不能使用的接口有:napi_set_property、napi_set_named_property、napi_define_properties。 |
禁止修改屬性類型,不能使用的接口有:napi_set_property、napi_set_named_property、napi_define_properties。 |
不支持Symbol相關接口和類型,不能使用的接口有:napi_create_symbol、napi_is_symbol_object、napi_symbol。 |
使用場景
Sendable對象可以在不同并發實例間通過引用傳遞。通過引用傳遞方式傳輸對象相比序列化方式更加高效,同時不丟失class上攜帶的成員方法,因此,Sendable主要可以解決兩個場景的問題: 1.?跨并發實例傳輸大數據(例如可能達到100KB以上) 2.?跨并發實例傳遞帶方法的class實例對象
跨并發實例傳輸大數據場景開發指導
由于跨并發實例序列化的開銷隨著數據量線性增長,因此當傳輸數據量較大時(100KB數據大約1ms傳輸耗時),跨并發實例的拷貝開銷大,影響并行化的性能。引用傳遞方式傳輸對象可提升性能。
示例:
// index.ets
import { taskpool } from '@kit.ArkTS';
import { testTypeA, testTypeB, Test } from './sendable'// 在并發函數中模擬數據處理
@Concurrent
async function taskFunc(obj: Test) {console.info("test task res1 is: " + obj.data1.name + " res2 is: " + obj.data2.name);
}async function test() {// 使用taskpool傳遞數據let a: testTypeA = new testTypeA("testTypeA");let b: testTypeB = new testTypeB("testTypeB");let obj: Test = new Test(a, b);let task: taskpool.Task = new taskpool.Task(taskFunc, obj);await taskpool.execute(task);
}test();
// sendable.ets
// 將數據量較大的數據在Sendable class中組裝
@Sendable
export class testTypeA {name: string = "A";constructor(name: string) {this.name = name;}
}@Sendable
export class testTypeB {name: string = "B";constructor(name: string) {this.name = name;}
}@Sendable
export class Test {data1: testTypeA;data2: testTypeB;constructor(arg1: testTypeA, arg2: testTypeB) {this.data1 = arg1;this.data2 = arg2;}
}
跨并發實例傳遞帶方法的class實例對象
由于序列化傳輸實例對象時會丟失方法,在必須調用實例方法的場景中,需使用引用傳遞方式進行開發。在數據處理過程中有需要解析的數據,可使用ASON工具進行數據解析。
示例:
// Index.ets
import { taskpool, ArkTSUtils } from '@kit.ArkTS'
import { SendableTestClass, ISendable } from './sendable'// 在并發函數中模擬數據處理
@Concurrent
async function taskFunc(sendableObj: SendableTestClass) {console.info("SendableTestClass: name is: " + sendableObj.printName() + ", age is: " + sendableObj.printAge() + ", sex is: " + sendableObj.printSex());sendableObj.setAge(28);console.info("SendableTestClass: age is: " + sendableObj.printAge());// 解析sendableObj.arr數據生成JSON字符串let str = ArkTSUtils.ASON.stringify(sendableObj.arr);console.info("SendableTestClass: str is: " + str);// 解析該數據并生成ISendable數據let jsonStr = '{"name": "Alexa", "age": 23, "sex": "female"}';let obj = ArkTSUtils.ASON.parse(jsonStr) as ISendable;console.info("SendableTestClass: type is: " + typeof obj);console.info("SendableTestClass: name is: " + (obj as object)?.["name"]); // 輸出: 'Alexa'console.info("SendableTestClass: age is: " + (obj as object)?.["age"]); // 輸出: 23console.info("SendableTestClass: sex is: " + (obj as object)?.["sex"]); // 輸出: 'female'
}
async function test() {// 使用taskpool傳遞數據let obj: SendableTestClass = new SendableTestClass();let task: taskpool.Task = new taskpool.Task(taskFunc, obj);await taskpool.execute(task);
}test();
// sendable.ets
// 定義模擬類Test,模仿開發過程中需傳遞帶方法的class
import { lang, collections } from '@kit.ArkTS'export type ISendable = lang.ISendable;@Sendable
export class SendableTestClass {name: string = 'John';age: number = 20;sex: string = "man";arr: collections.Array<number> = new collections.Array<number>(1, 2, 3);constructor() {}setAge(age: number) : void {this.age = age;}printName(): string {return this.name;}printAge(): number {return this.age;}printSex(): string {return this.sex;}
}