一、Worker 的运行机制
在 HarmonyOS Next 中,Worker 是一种轻量级的独立线程模型,它能长时间在后台运行,专门处理计算密集型或高延迟任务,从而避免主线程被阻塞。与 TaskPool 不同,Worker 的生命周期需要开发者手动管理,这赋予了它更强的可控性,适用于长期、有状态的复杂场景。
Worker 的主要作用是为应用程序提供一个多线程的运行环境,实现应用程序执行过程与宿主线程分离。通过在后台线程运行脚本处理耗时操作,避免计算密集型或高延迟任务阻塞宿主线程。
Worker 基于 Actor 并发模型,每个 Worker 线程拥有独立的内存空间,不与其他线程共享内存,仅通过消息传递进行通信。它的生命周期清晰且需手动管理,支持主线程与 Worker 线程间的双向通信。
Worker 是与主线程(宿主线程)并行运行的独立后台任务单元,其核心运行机制基于 Actor 并发模型,并通过"消息驱动"与"手动生命周期管理"来保障复杂、长时间任务的稳定高效运行。
二、Worker 的基本用法
① 创建 Worker 线程文件
手动创建:在 entry/src/main/ets/workers 目录下创建 MyWorker.ets,同时需要在 build-profile.json5 文件的 buildOption 中添加配置,确保 Worker 线程文件被打包到应用中。
javascript
复制代码
"buildOption": {
"sourceOption": {
"workers": [
'./src/main/ets/workers/worker.ets'
]
}
}
javascript
复制代码
"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/MainAbility/workers/worker.ets"
]
}
}
自动创建:在 DevEco Studio 工程目录中,右键选择 New -> Worker 即可一键生成模板文件及配置,即可自动生成 Worker 的模板文件及配置信息,无需再手动在 build-profile.json5中 进行相关配置。
在项目 entry/src/main/ets/workers 目录下创建一个 MyWorker.ts 文件,并在其中编写 Worker 线程的业务逻辑:
javascript
复制代码
// MyWorker.ts
import { worker } from '@kit.ArkTS';
// 获取当前 Worker 线程的通信端口
const parentPort = worker.workerPort;
// 监听主线程发来的消息
parentPort.onmessage = (event: MessageEvent) => {
const taskData = event.data;
console.info(`[Worker] 收到任务,数据: ${taskData}`);
// 在此处执行耗时任务
let result = 0;
// 模拟一个复杂计算
for (let i = 0; i < 10000; i++) {
result += taskData.value;
}
// 将计算结果发送回主线程
parentPort.postMessage({
taskId: taskData.taskId,
result: result
});
};
// 监听错误
parentPort.onerror = (err: ErrorEvent) => {
console.error(`[Worker] 发生错误: ${err.message}`);
};
② 在主线程中创建并调用 Worker
在应用的页面(如 Index.ets)中,实例化这个 Worker 并与其通信:
javascript
复制代码
import { worker } from '@kit.ArkTS';
@Entry
@Component
struct Index {
@State message: string = '';
aboutToAppear() {
// 1. 创建 Worker 实例,传入 Worker 脚本路径
const myWorker = new worker.ThreadWorker('entry/ets/workers/MyWorker.ts');
// 2. 监听 Worker 返回的结果
myWorker.onmessage = (event: MessageEvent) => {
const result = event.data;
this.message = `任务 ${result.taskId} 的计算结果是: ${result.result}`;
console.info(`[Main] 任务完成: ${this.message}`);
};
// 3. 监听 Worker 的错误
myWorker.onerror = (err: ErrorEvent) => {
console.error(`[Main] Worker 错误: ${err.message}`);
};
// 4. 向 Worker 发送任务消息
myWorker.postMessage({
taskId: 1001,
value: 100
});
}
build() {
Column() {
Text(this.message)
.fontSize(20)
.margin(20)
}
.width('100%')
.height('100%')
}
}
③ Worker 的核心 API
API
所属线程
类型
作用
示例
new worker.ThreadWorker()
宿主线程
同步
创建新的 Worker 实例并加载其脚本
new worker.ThreadWorker('entry/ets/workers/MyWorker.ets')
workerPort.postMessage()
Worker 线程
异步
从 Worker 线程向创建它的宿主线程发送消息
parentPort.postMessage(result)
workerPort.onmessage
Worker 线程
事件回调
设置在 Worker 线程中接收来自宿主线程消息的回调函数
parentPort.onmessage = (event) => { ... }
workerInstance.postMessage()
宿主线程
异步
从宿主线程向 Worker 线程发送消息
myWorker.postMessage({ value: 100 })
workerInstance.onmessage
宿主线程
事件回调
设置在宿主线程中接收来自 Worker 线程消息的回调函数
myWorker.onmessage = (event) => { ... }
workerInstance.onerror
宿主线程
事件回调
设置在宿主线程中接收 Worker 线程错误信息的回调函数
myWorker.onerror = (err) => { ... }
workerInstance.terminate()
宿主线程
同步
立即终止 Worker 线程,释放其占用的系统资源
myWorker.terminate()
parentPort.close()
Worker 线程
同步
通知 Worker 线程,使其在完成当前消息处理后安全地关闭自己
parentPort.close()
三、多级 Worker 生命周期管理
在 HarmonyOS Next 中,Worker 线程可以继续创建新的 Worker 线程,形成"父子Worker"的层级关系。这带来了更精细的分层任务调度能力,但也对生命周期管理提出了更高要求。
核心管理原则:
"从子到父"的销毁顺序:销毁父 Worker 前,必须确保所有子 Worker 已完全终止,这是多级 Worker 生命周期管理的最高原则。
确保父 Worker 在创建子 Worker 期间的存活:创建子 Worker 是一个异步过程,如果父 Worker 在子 Worker 初始化完成前就退出,会导致子 Worker 创建失败。
现有如下代码,会有什么风险?
javascript
复制代码
// 父Worker线程A的代码片段 (存在风险)
workerPort.onmessage = (event) => {
// 立即关闭父Worker自身,但此时子Worker还在创建中,会失败
workerPort.close();
let childWorker = new worker.ThreadWorker('entry/ets/workers/childWorker.ets');
};
从结果可以看出,可以导致子Worker(B)创建失败,因为父Worker(A)在子Worker(B)初始化完成前就退出了。可以通过 Promise 或 回调机制等方式,确保子 Worker 创建成功后再销毁父 Worker。
销毁多级 Worker 时,正确的流程为:Worker C (子) -> Worker B (父) -> Main Thread (主线程),可以使用类似下面的模式来保证顺序销毁:
javascript
复制代码
// Worker B (父Worker) 代码片段:管理子Worker C
let childWorker: worker.ThreadWorker | null = null;
createAndWaitForChild() {
return new Promise((resolve) => {
childWorker = new worker.ThreadWorker('entry/ets/workers/WorkerC.ets');
childWorker.onmessage = (event) => {
if (event.data.ready) {
console.log("Worker C is ready");
resolve();
}
};
});
}
async destroyChildren() {
// 1. 销毁所有子Worker C
if (childWorker) {
childWorker.terminate();
childWorker = null;
}
// 2. 可在此处执行其他清理工作...
}
四、Worker 和宿主线程的即时消息通信
① 异步消息驱动
在 HarmonyOS Next 中,Worker 与宿主线程(主线程或父 Worker)之间的通信遵循 Actor 模型:彼此拥有独立的内存空间,不共享数据,只能通过异步消息传递进行交互。这种设计天然避免了锁竞争和数据竞态,但消息的发送与接收是异步的,即 postMessage 调用后会立即返回,不等待对方处理。不过,对于大多数应用场景,这种异步通信已经足够"即时",因为系统会尽快将消息投递到目标线程的消息队列并调度执行。
Worker 与宿主线程之间的通信完全基于"消息队列"和"事件回调":
宿主线程 → Worker:宿主线程调用 workerInstance.postMessage(data) 将数据放入 Worker 的消息队列。Worker 线程在其事件循环中取出消息,触发 onmessage 回调。
Worker → 宿主线程:Worker 内部通过 parentPort.postMessage(data) 将结果发回宿主线程,宿主线程同样通过 onmessage 监听。
这种机制是全双工的,双方可以随时主动发送消息,且消息的投递顺序由系统保证(先进先出),但不保证处理完成的顺序(如果消息处理耗时不同)。
② 宿主 ↔ Worker 互发消息
以下示例演示了宿主主线程向 Worker 发送一个数字,Worker 立即计算平方并返回,宿主收到后再次发送确认消息,实现"一问一答"。创建 Worker 文件:entry/src/main/ets/workers/Calculator.ets:
javascript
复制代码
import { worker } from '@kit.ArkTS';
const parentPort = worker.workerPort;
parentPort.onmessage = (event: MessageEvent) => {
const received = event.data;
console.info(`[Worker] 收到消息: ${received}`);
if (typeof received === 'number') {
// 执行计算
const result = received * received;
// 立即发送结果回宿主
parentPort.postMessage({
type: 'square',
input: received,
output: result
});
} else if (received === 'ack') {
console.info('[Worker] 收到确认,工作完成');
}
};
parentPort.onerror = (err: ErrorEvent) => {
console.error(`[Worker] 错误: ${err.message}`);
};
javascript
复制代码
import { worker } from '@kit.ArkTS';
@Entry
@Component
struct Index {
@State message: string = '';
aboutToAppear() {
// 创建 Worker
const calcWorker = new worker.ThreadWorker('entry/ets/workers/Calculator.ets');
// 监听 Worker 发回的消息
calcWorker.onmessage = (event: MessageEvent) => {
const data = event.data;
if (data.type === 'square') {
this.message = `${data.input} 的平方是 ${data.output}`;
console.info(`[Main] 收到计算结果: ${data.output}`);
// 可选:回复确认消息(即时通信演示)
calcWorker.postMessage('ack');
}
};
calcWorker.onerror = (err) => {
console.error(`[Main] Worker 错误: ${err.message}`);
};
// 发送任务到 Worker
calcWorker.postMessage(10);
}
build() {
Column({ space: 20 }) {
Text(this.message || '等待计算结果...')
.fontSize(18)
}
.padding(20)
.width('100%')
.height('100%')
}
}
运行结果:页面会显示 "10 的平方是 100",控制台打印双方的消息。
③ 如何实现"同步等待"消息?
Worker 通信天生异步,但有时业务需要等待 Worker 返回结果后再继续执行,可以使用 Promise 封装,将回调转换为 async/await 风格:
javascript
复制代码
askWorker(worker: worker.ThreadWorker, data: any): Promise<any> {
return new Promise((resolve, reject) => {
const handler = (event: MessageEvent) => {
worker.off('message', handler); // 只接收一次
resolve(event.data);
};
worker.on('message', handler);
worker.onerror = (err) => reject(err);
worker.postMessage(data);
});
}
// 使用
const result = await askWorker(calcWorker, 10);
console.log('同步等待结果:', result);
这种方式并不会阻塞线程,而是用异步等待,符合鸿蒙主线程不能阻塞的要求。
④ 数据传输类型的优化
普通对象:深拷贝,适合小数据。
ArrayBuffer:可以转移所有权(零拷贝)以提高性能:
javascript
复制代码
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]); // 转移 buffer,主线程中不再可用
SharedArrayBuffer:共享内存,配合 Atomics 实现真正的同步通信(需注意线程安全)。
需要注意的是:
消息不一定"即时":如果 Worker 正在执行耗时同步任务,后续消息会被排队,直到当前任务完成。因此避免在 Worker 中执行超长同步循环,以免阻塞消息处理。
错误处理必须完善:Worker 内部未捕获的异常会触发 onerror,但不会导致应用崩溃。务必监听 onerror 并采取恢复措施(如重启 Worker)。
及时终止 Worker:Worker 线程会持续占用内存,当不再需要时调用 terminate() 释放资源。
消息大小限制:单次 postMessage 的数据大小不能超过 16 MB(API 12 及以上)。大数据建议使用 ArrayBuffer 转移或分片发送。
Worker 内无法访问 UI:任何 UI 组件、Canvas、promptAction 等都不能在 Worker 中使用。
Worker 与宿主线程的即时消息通信基于异步消息队列实现,双方通过 postMessage 和 onmessage 收发数据。借助 Promise 可以模拟同步等待,但本质仍是非阻塞的。合理设计任务分片和错误处理,可以构建高性能的多线程通信应用。