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个。多了反而会因为线程切换和内存竞争拖慢整体性能。记住,并发不是万能的,合适的并发才是