鸿蒙ArkTS并发编程指南
包括、、、、、。
ArkTS并发官方指南,包括并发概述、异步并发 (Promise和async/await)、多线程并发、并发线程间通信、应用多线程开发实践、并发常见问题。
并发概述
为了提升应用的响应速度和帧率,避免耗时任务影响UI主线程,ArkTS提供了异步和多线程两种处理策略。
-
异步是指异步代码在执行到一定程度后暂停,并在未来某个时间点继续执行,同一时间只有一段代码执行。ArkTS通过Promise和async/await提供异步并发能力,适用于单次I/O任务。详细请参见使用异步并发能力。
-
多线程允许同时执行多段代码。UI主线程继续响应用户操作和更新UI,后台线程执行耗时操作,避免应用卡顿。ArkTS通过TaskPool和Worker提供多线程能力,适用于耗时任务等场景。详细请参见多线程并发概述。
在多线程场景下,不同线程间需要进行数据通信。不同类别的对象采用不同的传输方式,如拷贝或内存共享。
并发能力广泛应用于多种场景,包括异步并发任务、耗时任务(如CPU密集型任务、I/O密集型任务和同步任务等)、长时任务、常驻任务等。开发者可以根据不同的任务诉求和场景,选择相应的并发策略进行优化和开发,具体案例可以参见应用多线程开发实践案例。
异步 (Promise和async/await)
Promise
概述
Promise是一种用于处理异步操作的对象,可将异步操作转换为类似同步操作的风格,便于代码编写和维护。Promise通过状态机制管理异步操作的不同阶段,有三种状态:pending(进行中)、fulfilled(已完成,也叫resolved)和rejected(已拒绝)。创建后处于pending状态,异步操作完成后转换为fulfilled或rejected状态。
Promise提供了then、catch、finally方法来注册回调函数,以处理异步操作的成功或失败结果。当Promise状态改变时,回调函数会被加入微任务队列等待执行,依赖事件循环机制在宏任务执行完成后优先执行微任务,从而保证回调函数的异步调度。
创建Promise对象
最基本的用法是通过构造函数实例化一个Promise对象,传入一个带有两个参数的函数,称为executor函数。executor函数接收两个参数:resolve和reject,分别表示异步操作成功和失败时的回调函数。
//创建了一个Promise对象并模拟了一个异步操作
const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => {
setTimeout(() => {
const randomNumber: number = Math.random();
if (randomNumber > 0.5) {
resolve(randomNumber);
} else {
reject(new Error('Random number is too small'));
}
}, 1000);
})
在上述代码中,setTimeout函数模拟了一个异步操作,1秒后生成一个随机数。如果随机数大于0.5,调用resolve回调函数并传递该随机数;否则调用reject回调函数并传递一个错误对象。
使用Promise对象
Promise对象创建后,可以使用then方法和catch方法指定fulfilled状态和rejected状态的回调函数。then方法可接受两个参数,一个处理fulfilled状态的函数,另一个处理rejected状态的函数。只传一个参数则表示当Promise对象状态变为fulfilled时,then方法会自动调用这个回调函数,并将Promise对象的结果作为参数传递给它。使用catch方法注册一个回调函数,用于处理“失败”的结果,即捕获Promise的状态改变为rejected状态或操作失败抛出的异常。Promise还可以使用finally注册回调函数,无论Promise最终状态如何(fulfilled或rejected),都会执行该回调函数。
const promise: Promise<number> = new Promise((resolve: Function, reject: Function) => {
setTimeout(() => {
const randomNumber: number = Math.random();
if (randomNumber > 0.5) {
resolve(randomNumber);
} else {
reject(new Error('Random number is too small'));
}
}, 1000);
})
// 使用 then 方法定义成功和失败的回调
promise.then((result: number) => {
console.info(`The number for success is ${result}`); // 成功时执行
}, (error: Error) => {
console.error(error.message); // 失败时执行
}
);
// 使用 then 方法定义成功的回调,catch 方法定义失败的回调
promise.then((result: number) => {
console.info(`Random number is ${result}`); // 成功时执行
}).catch((error: Error) => {
console.error(error.message); // 失败时执行
});
// 无论成功还是失败都会执行
promise.finally(() => {
console.info('finally complete');
})
说明
当Promise被reject且未通过catch方法处理时,会触发globalUnhandledRejectionDetected事件。可使用errorManager.on('globalUnhandledRejectionDetected')接口监听该事件,以全局捕获未处理的Promise reject。
async/await
async/await作用是将异步操作转同步执行。可以结合Promise/多线程(如TaskPool)使用。
| 关键字 | 主要作用 |
|---|---|
async |
声明一个函数是异步函数。这个函数内部可以使用 await,并且其返回值会被自动包装成一个 Promise 对象。 |
await |
在 async 函数内部使用,用于等待一个 Promise 对象的完成。它会暂停当前异步函数的执行,直到其后的 Promise 被解决(成功或失败),然后返回结果。 |
// 1. 使用 async 关键字定义一个异步函数
async function fetchUserData() {
try {
// 2. 在函数内部,使用 await 等待一个返回 Promise 的异步操作
const response = await httpRequest.request(
"https://api.example.com/user/1",
{ method: http.RequestMethod.GET }
);
// 3. 当 await 等待的 Promise 成功解决后,代码会继续向下执行
console.log("请求结果:", response.result);
return response.result; // 这个返回值会被自动包装为 Promise
} catch (err) {
// 4. 使用 try/catch 来捕获异步操作中可能发生的错误
console.error("出错了:", err);
}
}
// 5. 调用这个异步函数
fetchUserData();
import { taskpool } from '@ohos.taskpool';
async function handleUserClick() {
// 1. UI线程快速响应,显示加载指示器
showLoadingIndicator();
// 2. 将重型CPU任务丢到taskpool(不会阻塞UI)
const heavyTask = new taskpool.Task(heavyComputation, data);
try {
// 3. 等待taskpool在后台线程完成计算(逻辑等待,物理并行)
const result = await taskpool.execute(heavyTask);
// 4. 回到UI线程更新界面
updateUIWithResult(result);
} catch (error) {
handleError(error);
} finally {
hideLoadingIndicator();
}
}
emitter
在鸿蒙(HarmonyOS)应用开发中,Emitter 是一个进程内的事件处理机制,用于在同一进程的不同线程间或同一线程内部进行事件通信。它采用了常见的发布-订阅模式,让组件或模块之间能够方便地解耦通信。emitter官方API文档
在鸿蒙(HarmonyOS)应用开发中,使用Emitter进行事件通信时,直接在事件数据中定义方法是不可行的。事件数据在传递过程中需要被序列化,而方法(函数)无法被序列化。
不过,根据官方文档的说明,从API version 12开始,Emitter提供了泛型接口,允许你传递一个类实例,并在回调中调用该实例的方法。下面这个表格清晰地展示了这两种方式的区别:
| 特性 | 直接定义方法 (不可行) | 传递类实例 (API 12+ 可行方案) |
|---|---|---|
| 实现方式 | 直接在EventData的data对象里定义方法 |
使用GenericEventData<T>,data为泛型类型T的实例 |
| 数据序列化 | ❌ 方法无法被序列化,导致传递失败 | ✅ 传递的是类实例,在回调中可调用其方法 |
| 类型安全 | ❌ 无类型约束 | ✅ 提供泛型支持,类型更安全 |
| 适用场景 | - | 需要将数据和行为一同封装传递的复杂场景 |
如何传递与调用方法
虽然不能直接定义方法,但你可以通过传递类实例来达到调用方法的目的。以下是基于API version 12及以上版本的实现步骤和示例:
-
定义一个类:这个类包含你所需的数据以及方法。
-
使用泛型接口发送事件:发送事件时,使用
emitter.emit<T>并指定泛型类型为你定义的类。事件数据data字段就是这个类的实例。 -
在接收方处理并调用方法:在订阅事件时,使用
emitter.on<T>指定相同的泛型类型。在回调函数中,判断data是否为该类的实例,然后就可以安全地调用其方法了。
// 引入Emitter模块
import { emitter } from '@kit.BasicServicesKit';
// 1. 定义一个类,这个类需要被标记为@Sendable
@Sendable
class SampleData {
count: number = 100; // 数据成员
// 类的方法
printCount(): void {
console.info('Print count: ' + this.count);
}
}
// 2. 发送事件的一方
function sendEventWithMethod() {
// 创建类的实例
let sampleInstance = new SampleData();
sampleInstance.count = 200; // 可以设置数据
// 使用泛型接口发送事件,指定泛型T为SampleData
let eventData: emitter.GenericEventData<SampleData> = {
data: sampleInstance // 将实例放入data字段
};
// 发送事件,eventId需与接收方一致
emitter.emit("myEvent", eventData);
}
// 3. 接收事件的一方
// 订阅事件,同样使用泛型接口指定类型
let callback: Callback<emitter.GenericEventData<SampleData>> = (eventData: emitter.GenericEventData<SampleData>): void => {
console.info(`Received event data.`);
// 关键步骤:检查data是否为SampleData的实例
if (eventData?.data instanceof SampleData) {
// 如果是,则可以安全地调用其方法
eventData.data.printCount(); // 将输出 "Print count: 200"
}
};
// 注册事件监听
emitter.on("myEvent", callback);
注意事项
在实现过程中,有以下几个关键点需要特别注意:
-
实例检查:在接收事件的回调函数中,必须使用
instanceof来检查eventData.data是否是你的目标类实例。这是因为Emitter可能接收到不同结构的事件,进行类型判断可以保证代码的健壮性。 -
生命周期管理:务必在组件或页面的生命周期结束时(例如
aboutToDisappear中),使用emitter.off取消事件订阅,以防止内存泄漏。 emit()支持跨线程传输数据,但不支持@State、@Observed等装饰器修饰的复杂类型。且需遵循线程间通信对象约束。
线程间通信对象约束
基于鸿蒙(HarmonyOS)ArkTS开发的线程间通信对象约束如下线程间通信对象概述:
一、序列化通用约束
-
数据量限制
单次序列化传输的数据量大小不得超过 16MB 1。 -
装饰器限制
不支持序列化使用以下装饰器修饰的复杂类型: 1@State@Prop@Link
二、对象类型约束
1. 普通对象
- 支持传递基本数据类型(如
number、string)。 - 支持自定义对象,但需满足 序列化规范(即对象属性需为可序列化类型)。
2. ArrayBuffer对象
- 适用于传递大段连续内存数据(如图片、音频原始数据)1。
- 通过深拷贝实现二进制数据的高效传递。
3. SharedArrayBuffer对象
- 支持多线程共享内存,允许线程间直接访问同一块内存区域1。
- 注意事项:需避免多线程并发修改导致的数据竞争问题。
4. Transferable对象(如NativeBinding对象)1
- 支持跨线程转移对象所有权(如文件描述符、图形资源)。
- 转移后原线程 不再拥有访问权限。
5. Sendable对象
- 必须通过
@Sendable装饰器显式标记。 - 需满足 Sendable约束规则:
- 对象属性类型必须为基本类型、
Sendable对象或不可变对象。 - 禁止包含闭包或非
Sendable的复杂类型。
- 对象属性类型必须为基本类型、
三、通信行为约束
-
普通JS对象
通过 Structured Clone算法 进行深拷贝(序列化与反序列化),效率较低2。 -
SharedArrayBuffer
直接共享内存,无深拷贝开销,但需自行管理线程安全。 -
Transferable对象
所有权转移后,原线程访问将抛出异常。
更多推荐



所有评论(0)