3、Worker工作线程:Worker基本概念、创建与通信、生命周期管理
说到多线程,很多同学第一反应就是线程池、锁、同步。但在鸿蒙里,有个东西你绕不开——Worker工作线程。我最早接触它的时候,心里还嘀咕:这不就是Android的Service吗?后来踩了几个坑才明白,它俩完全是两码事。
说白了,Worker是鸿蒙提供的一种轻量级并发方案。它不跑在主线程上,有自己的消息队列和生命周期。你想想看,如果你的App要做一个大文件解析,或者要跑一个复杂的计算任务,直接扔主线程?那界面肯定卡成PPT。这时候,Worker就派上用场了。
3.1 Worker基本概念
Worker,顾名思义,就是干活的。它独立于主线程运行,有自己的JS执行环境。我习惯把它理解成一个「小号的独立进程」——虽然它和主进程共享同一个进程空间,但内存是不共享的,只能通过消息传递来通信。
核心特点:
- 每个Worker有独立的JS引擎实例
- 不能直接操作UI,不能访问主线程的DOM
- 通过postMessage和onmessage进行通信
- 支持多Worker并行,但注意资源开销
我在项目中遇到过一个问题:有个同事把Worker当线程池用,一口气创建了十几个Worker。结果呢?内存直接飙上去,手机发烫。嗯,这里要注意——Worker不是免费的,每个Worker都有自己的堆栈和上下文,创建太多反而得不偿失。
3.2 创建与通信
创建Worker其实很简单。鸿蒙提供了worker.worker接口,你只需要指定一个脚本文件路径就行。
// 主线程中创建Worker
import worker from '@ohos.worker';
const myWorker = new worker.Worker('entry/src/main/ets/workers/MyWorker.ets', {
name: 'MyWorker'
});
// 发送消息给Worker
myWorker.postMessage({ type: 'start', data: 'hello' });
// 接收Worker返回的消息
myWorker.onmessage = (event) => {
console.info('收到Worker消息:', event.data);
};
// 错误处理
myWorker.onerror = (error) => {
console.error('Worker出错:', error.message);
};
在Worker脚本里,你需要这样写:
// MyWorker.ets
import worker from '@ohos.worker';
const parentPort = worker.workerPort;
// 接收主线程消息
parentPort.onmessage = (event) => {
const msg = event.data;
console.info('Worker收到:', msg);
// 处理耗时任务
const result = doHeavyWork(msg.data);
// 把结果发回主线程
parentPort.postMessage({ type: 'result', data: result });
};
function doHeavyWork(input) {
// 模拟耗时计算
let sum = 0;
for (let i = 0; i < 1000000; i++) {
sum += i;
}
return sum;
}
我的小技巧:通信数据尽量用JSON序列化,别传复杂对象。我曾经传了一个带循环引用的对象,结果Worker那边收到的是undefined,排查了半天才发现是序列化失败。
3.3 Worker生命周期管理
Worker的生命周期其实就四个阶段:创建 → 运行 → 等待 → 销毁。但很多人只关心创建和通信,忽略了销毁,结果内存泄漏了都不知道。
| 阶段 | 状态 | 说明 |
|---|---|---|
| 创建 | initializing | Worker实例化,加载脚本 |
| 运行 | running | Worker正在执行任务 |
| 等待 | idle | Worker空闲,等待消息 |
| 销毁 | terminated | Worker被终止,释放资源 |
销毁Worker有两种方式:
// 方式一:主线程主动终止
myWorker.terminate();
// 方式二:Worker内部自行退出
parentPort.close();
注意:Worker一旦被terminate或close,就不能再复用了。如果你想再次使用,必须重新new一个Worker实例。我见过有人试图给已销毁的Worker发消息,结果直接报错——嗯,这个坑我踩过。
我个人习惯的做法是:在Worker完成任务后,主动调用close(),然后在主线程监听onclose事件做清理工作。这样能确保资源及时释放,不会出现僵尸Worker。
// 主线程监听Worker关闭
myWorker.onclose = () => {
console.info('Worker已关闭,清理资源');
myWorker = null; // 释放引用
};
你可能会问:什么时候该用Worker?我总结了几种典型场景:
- 大文件解析:比如解析CSV、JSON大文件,避免阻塞UI
- 图片处理:压缩、裁剪、滤镜等计算密集型操作
- 数据加密/解密:对称加密、哈希计算等
- 后台数据同步:定期拉取服务器数据,更新本地缓存
最后说一句:Worker虽好,但别滥用。我建议一个App里同时运行的Worker不要超过3-4个。多了反而会因为线程切换和内存竞争拖慢整体性能。记住,并发不是万能的,合适的并发才是。