☰
【HarmonyOS开发小实践】Worker 创建、生命周期与多级 Worker
2026/9/30 8:46:14 网站建设 项目流程

Worker 创建、生命周期与多级 Worker

TaskPool 用起来简单,但碰到要长时间占据线程、需要保存句柄状态、或者任务超过 3 分钟的场景,就得请出 Worker 了。Worker 给你一个独立的运行环境,自己管生命周期,自己跟主线程消息通信,自由度更高,代价就是写起来更繁琐。这篇文章把 Worker 的运作机制、创建方式、文件路径规则、生命周期管理和多级 Worker 用法都过一遍。

Worker 的运作机制

Worker 子线程拥有独立的 ArkTS Runtime 实例,包括独立的内存空间、消息队列(MessageQueue)、事件轮询机制(EventLoop)、调用栈(CallStack)。和主线程一样是个完整的执行环境,只是没有 UI 能力。

Worker 线程

主线程

postMessage
序列化

postMessage
序列化

onMessage

onMessage

ArkTS Main Thread
独立 Runtime

MessageQueue

ArkTS Worker Thread
独立 Runtime

MessageQueue

主线程和 Worker 线程通过postMessage互相发消息,数据通过序列化传输。每个 Worker 启动都有内存开销(独立的 Runtime 实例),所以系统限制了 Worker 数量上限。

多核 CPU 上多个 Worker 线程可以真正并行执行,这是真并发,不是时间片轮转。

创建 Worker

Worker 线程文件必须放在{moduleName}/src/main/ets/目录层级之下,否则不会被打包到应用里。创建方式有两种:

自动创建(推荐)。在 DevEco Studio 里右键{moduleName}目录下任意位置 > New > Worker,自动生成模板文件和 build-profile.json5 配置,省事。

手动创建。自己建文件,然后在 build-profile.json5 里配置:

// Stage 模型"buildOption":{"sourceOption":{"workers":["./src/main/ets/workers/worker.ets"]}}
// FA 模型"buildOption":{"sourceOption":{"workers":["./src/main/ets/MainAbility/workers/worker.ets"]}}

漏配的话 Worker 文件不会被打包,运行时找不到文件会报错。

文件路径规则

构造 Worker 实例时要传入 Worker 线程文件路径(scriptURL)。Stage 模型下有三种写法:

写法一:{moduleName}/ets/{relativePath}

import{worker}from'@kit.ArkTS';// 文件在 entry/src/main/ets/workers/worker.etsconstworker1:worker.ThreadWorker=newworker.ThreadWorker('entry/ets/workers/worker.ets');// 文件在 testworkers/src/main/ets/ThreadFile/workers/worker.etsconstworker2:worker.ThreadWorker=newworker.ThreadWorker('testworkers/ets/ThreadFile/workers/worker.ets');

写法二:@{moduleName}/ets/{relativePath}

import{worker}from'@kit.ArkTS';// 加载 har 包里的 Worker,文件在 har/src/main/ets/workers/worker.etsconstworker3:worker.ThreadWorker=newworker.ThreadWorker('@har/ets/workers/worker.ets');

写法三:相对路径(仅包内,不支持跨包)

import{worker}from'@kit.ArkTS';// 当前文件在 har/src/main/ets/components/mainpage/MainPage.ets// Worker 文件在 har/src/main/ets/workers/worker.etsconstworker4:worker.ThreadWorker=newworker.ThreadWorker('../../workers/worker.ets');

跨包加载规则比较复杂,整理成一张表:

加载方\被加载方entryfeature应用内 hsp跨工程 hsp源码 har三方 har
entry写法一、三写法一写法一不支持写法二不支持
feature不支持跨包写法一,包内写法一、三写法一不支持写法二不支持
应用内 hsp不支持写法一跨包写法一,包内写法一、三不支持写法二不支持
跨工程 hsp不支持不支持不支持不支持不支持不支持
源码 har不支持写法一写法一不支持跨包写法二,包内写法二、三不支持
三方 har不支持不支持不支持不支持不支持仅包内写法三

几个注意点:

  • 加载 entry、feature、hsp 包的 Worker 不建议用写法三,推荐写法一,不用拼路径。
  • 文件路径后缀.ets/.ts可以省略。
  • 跨 HSP/HAR 包要在 oh-package.json5 里配依赖项。
  • 开启useNormalizedOHMUrl或 HAR 包被打包成三方包时,HAR 包里 Worker 只能用相对路径创建。

FA 模型下 scriptURL 是 Worker 文件相对于{moduleName}/src/main/ets/MainAbility的路径:

import{worker}from'@kit.ArkTS';// 文件在 {moduleName}/src/main/ets/MainAbility/workers/worker.etsconstworkerFA1=newworker.ThreadWorker('workers/worker.ets');// 文件在 {moduleName}/src/main/ets/workers/worker.etsconstworkerFA2=newworker.ThreadWorker('../workers/worker.ets');

生命周期管理

Worker 创建后需要手动管生命周期。创建和销毁开销不小,建议复用而不是频繁创建。空闲 Worker 仍然占资源,不用了主动调terminate()或close()销毁。

new ThreadWorker()

postMessage 触发

消息收发

terminate() / close()

onexit 回调完成

Created

Running

Terminating

Terminated

几个关键点:

  • terminate()/close()是异步退出。注册的onexit()回调执行完线程才真正退出。
  • Worker 已销毁或正在销毁时调功能接口会抛错。
  • 数量上限:内存允许时最多 64 个 Worker,加上 napi_create_ark_runtime 创建的 runtime 总数不超过 80。超限报错Worker initialization failure, the number of workers exceeds the maximum.
  • 内存阈值:1.5GB 和设备物理内存 60% 中较小值。所有 Worker + 主线程累积内存超阈值会触发 OOM 崩溃。

基本用法示例

主线程:

import{ErrorEvent,MessageEvents,worker}from'@kit.ArkTS';@Entry@Componentstruct Index{build(){Column(){Button('start').onClick(()=>{constworkerInstance=newworker.ThreadWorker('entry/ets/workers/worker.ets');// 接收 Worker 发来的消息,在主线程执行workerInstance.onmessage=(e:MessageEvents)=>{console.info(`onmessage:${e.data}`);};// 捕获 Worker 内全局异常,在主线程执行workerInstance.onAllErrors=(err:ErrorEvent)=>{console.error(`onAllErrors:${err.message}`);};// 接收到无法序列化的消息时调用workerInstance.onmessageerror=()=>{console.error('onmessageerror');};// Worker 销毁时调用,code=0 正常退出,code=1 异常退出workerInstance.onexit=(code:number)=>{console.info(`onexit code:${code}`);};// 发消息给 WorkerworkerInstance.postMessage('1');})}}}

Worker 文件 worker.ets:

import{ErrorEvent,MessageEvents,ThreadWorkerGlobalScope,worker}from'@kit.ArkTS';constworkerPort:ThreadWorkerGlobalScope=worker.workerPort;// 收到主线程消息,在 Worker 线程执行workerPort.onmessage=(e:MessageEvents)=>{console.info('workerPort onmessage: ',e.data);// 给主线程回消息workerPort.postMessage('2');};workerPort.onmessageerror=()=>{console.error('workerPort onmessageerror');};workerPort.onerror=(err:ErrorEvent)=>{console.error('workerPort onerror: ',err.message);};

主线程和 Worker 线程的回调是对称的:主线程有onmessage/onAllErrors/onmessageerror/onexit,Worker 线程有onmessage/onmessageerror/onerror。onAllErrors只在主线程侧有,能捕获 Worker 线程里 onmessage、timer 回调以及文件执行等流程的全局异常。

多级 Worker

Worker 可以创建子 Worker,形成层级关系。父 Worker 在自己的 onmessage 里new worker.ThreadWorker(...)就能创建子 Worker。但生命周期管理要特别小心:销毁父 Worker 前必须先销毁所有子 Worker,否则会有不可预期的结果。

推荐写法

宿主线程:

import{worker,MessageEvents,ErrorEvent}from'@kit.ArkTS';constparentWorker=newworker.ThreadWorker('entry/ets/workers/ParentWorker.ets');parentWorker.onmessage=(e:MessageEvents)=>{console.info('宿主线程收到父Worker消息 '+e.data);};parentWorker.onexit=()=>{console.info('父Worker退出');};parentWorker.onAllErrors=(err:ErrorEvent)=>{console.error('父Worker报错 '+err.message);};parentWorker.postMessage('宿主线程发送消息给父Worker');

ParentWorker.ets:

import{ErrorEvent,MessageEvents,ThreadWorkerGlobalScope,worker}from'@kit.ArkTS';constworkerPort:ThreadWorkerGlobalScope=worker.workerPort;workerPort.onmessage=(e:MessageEvents)=>{if(e.data==='宿主线程发送消息给父Worker'){constchildWorker=newworker.ThreadWorker('entry/ets/workers/ChildWorker.ets');childWorker.onmessage=(e:MessageEvents)=>{console.info('父Worker收到子Worker消息 '+e.data);if(e.data==='子Worker向父Worker发送信息'){workerPort.postMessage('父Worker向宿主线程发送信息');}};// 关键:子 Worker 退出后再销毁父 WorkerchildWorker.onexit=()=>{console.info('子Worker退出');workerPort.close();};childWorker.onAllErrors=(err:ErrorEvent)=>{console.error('子Worker报错 '+err.message);};childWorker.postMessage('父Worker向子Worker发送信息');}};

ChildWorker.ets:

import{MessageEvents,ThreadWorkerGlobalScope,worker}from'@kit.ArkTS';constworkerPort:ThreadWorkerGlobalScope=worker.workerPort;workerPort.onmessage=(e:MessageEvents)=>{if(e.data==='父Worker向子Worker发送信息'){console.info('业务执行结束');workerPort.postMessage('子Worker向父Worker发送信息');// 子 Worker 任务完成后主动退出workerPort.close();}};

销毁顺序:子 Workerclose()→ 触发子 Workeronexit→ 在子 Workeronexit里调父 Workerclose()→ 触发父 Workeronexit。这样保证父 Worker 销毁时子 Worker 已经不在了。

反例 1:父 Worker 销毁后子 Worker 还在发消息

// ParentWorker.ets —— 错误示范workerPort.onmessage=(e:MessageEvents)=>{constchildWorker=newworker.ThreadWorker('entry/ets/workers/ChildWorker.ets');childWorker.onmessage=(e:MessageEvents)=>{console.info('父Worker收到子Worker消息 '+e.data);};childWorker.onexit=()=>{// 父 Worker 已经或即将退出,再通过父 Worker 端口发消息会出问题workerPort.postMessage('父Worker向宿主线程发送信息');};childWorker.postMessage('父Worker向子Worker发送信息');// 创建子 Worker 后立刻销毁父 Worker,子 Worker 还在跑workerPort.close();};
// ChildWorker.ets —— 错误示范workerPort.onmessage=(e:MessageEvents)=>{// 父 Worker 销毁后还往父 Worker 发消息workerPort.postMessage('子Worker向父Worker发送信息');setTimeout(()=>{workerPort.postMessage('再发一次');// 父 Worker 已经没了},1000);};

父 Worker 已经销毁,子 Worker 发的消息没人接,行为不可预期。

反例 2:父 Worker 发起销毁后再创建子 Worker

// ParentWorker.ets —— 错误示范workerPort.onmessage=(e:MessageEvents)=>{workerPort.close();// 先发起销毁// 父 Worker 正在退出,又创建子 WorkerconstchildWorker=newworker.ThreadWorker('entry/ets/workers/ChildWorker.ets');childWorker.postMessage('...');// 父 Worker 可能已经没了};

创建子 Worker 前要确保父 Worker 处于存活状态。先close()再new ThreadWorker()顺序就反了。

实践中要注意的

手动管理生命周期。Worker 没有 TaskPool 那种自动扩缩容。创建销毁开销大,复用为主。不用了一定要terminate()或close(),不然空闲 Worker 一直占内存。

数量上限 64 个。加上 napi runtime 总数不超过 80。超了直接报错。任务量大的场景用 TaskPool 更合适。

内存阈值 1.5GB。实际可用数量根据内存动态调整。所有 Worker + 主线程累积内存超阈值会 OOM 崩溃。监控内存占用,及时销毁不用的 Worker。

只能用线程安全的库。Worker 线程不能操作 UI,不能用线程不安全的模块。AppStorage 也不支持在 Worker 里用。

16MB 序列化限制。单次postMessage数据量上限 16MB。大数据用 ArrayBuffer 转移或 Sendable。

Worker 文件里禁止 export。在 Worker 文件里export任何内容会导致 jscrash。Worker 文件就是个执行体,不是模块。

应用切后台 Worker 暂停。应用挂起切到后台后,Worker 线程会暂停运行。回来后恢复。如果任务有时效要求,注意这个行为。

terminate 是异步的。调完terminate()后线程不是立刻退出,要等onexit回调执行完。在这期间调 Worker 接口会抛错。需要等销毁完成再做后续操作的话,把逻辑放在onexit里。

多级 Worker 销毁顺序。销毁父 Worker 前先销毁所有子 Worker。推荐在子 Worker 的onexit里调父 Worker 的close(),保证顺序正确。

和 TaskPool 的根本差异是虾米

Worker 给你一个完整的独立 Runtime,自己管生命周期,适合长时任务、需要保存状态、依赖线程上下文的场景。TaskPool 是线程池 + 调度器,自动管理,适合独立短任务。Worker 自由度高,代价是代码量和心智负担;TaskPool 简单,但有 3 分钟和 16MB 的硬限制。

选型其实不复杂:任务超过 3 分钟、需要保存句柄状态、内存敏感,用 Worker;其他场景优先 TaskPool。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询