适用版本:HarmonyOS 6.1(API 12)及以上
验证环境:Pura 90 Pro 模拟器(HarmonyOS 6.1.1,API 24)
关键 API@kit.ArkTSworker.ThreadWorkerworker.workerPort


前言

上一篇我们用 TaskPool 完成了一次性并发任务,本篇聚焦 Worker——它的核心差异在于:Worker 是一个独立执行环境(独立 JS 引擎实例),拥有自己的内存空间,与主线程通过 postMessage 消息队列双向通信。适合长驻后台、需要持续交互的场景,如 WebSocket 数据处理、音频流解析、大文件边读边处理。


TaskPool vs Worker 核心对比

维度 TaskPool Worker
生命周期 任务完成即结束 手动 terminate()
通信方式 函数参数/返回值 postMessage 消息队列
进度上报 ❌ 不支持 ✅ 随时 postMessage
内存隔离 任务间隔离 与主线程完全隔离
适用场景 一次性计算 长驻/流式处理
线程数管理 系统自动 开发者控制

架构图

主线程(UI)                    Worker 线程(独立 JS 引擎)
    │                                    │
    │  new ThreadWorker('PrimeWorker.ets')│
    │─────────────────────────────────── ▶│  Worker 初始化
    │                                    │
    │  postMessage({ limit: 300000 })    │
    │─────────────────────────────────── ▶│  ctx.onmessage 触发
    │                                    │  开始筛质数…
    │                                    │
    │ ◀─── postMessage({ type:'progress'})│  进度上报(可多次)
    │  onmessage → 更新 UI               │
    │                                    │
    │ ◀─── postMessage({ type:'done'})   │  任务完成
    │  onmessage → 展示结果              │
    │  workerInstance.terminate()         │  Worker 销毁

完整代码

主线程:Index.ets

import { worker, MessageEvents, ErrorEvent } from '@kit.ArkTS'

type WorkerStatus = 'idle' | 'running' | 'done' | 'error'

class LogEntry {
  time: string = ''
  msg: string = ''
  isWorker: boolean = false
}

@Entry
@Component
struct WorkerDemo {
  @State status: WorkerStatus = 'idle'
  @State logs: LogEntry[] = []
  @State result: string = ''
  @State elapsed: number = 0
  private workerInstance: worker.ThreadWorker | null = null
  private startTime: number = 0

  private startWorker(): void {
    if (this.status === 'running') { return }
    this.status = 'running'
    this.result = ''
    this.startTime = Date.now()

    // 创建 Worker,路径相对于 entry/src/main/ets/
    this.workerInstance = new worker.ThreadWorker('entry/ets/workers/PrimeWorker.ets')

    // 接收 Worker 消息
    this.workerInstance.onmessage = (event: MessageEvents) => {
      const data = event.data as Record<string, number | string>
      const msgType = data['type'] as string

      if (msgType === 'progress') {
        this.addLog(`Worker:已处理到 ${data['current']}`, true)
      } else if (msgType === 'done') {
        this.elapsed = Date.now() - this.startTime
        this.result = `质数个数: ${data['count']}`
        this.status = 'done'
        this.addLog(`主线程:收到结果 → ${this.result}${this.elapsed}ms)`)
        // 任务完成后必须 terminate,否则 Worker 进程驻留
        this.workerInstance?.terminate()
        this.workerInstance = null
      }
    }

    // Worker 错误处理
    this.workerInstance.onerror = (event: ErrorEvent) => {
      this.status = 'error'
      this.addLog(`Worker 错误: ${event.message}`)
      this.workerInstance?.terminate()
      this.workerInstance = null
    }

    // 发送任务给 Worker
    this.addLog('主线程:发送任务 → limit=300000')
    this.workerInstance.postMessage({ limit: 300000 })
  }

  private stopWorker(): void {
    this.workerInstance?.terminate()  // 强制终止
    this.workerInstance = null
    this.status = 'idle'
    this.addLog('主线程:已终止 Worker')
  }

  // ... build() 略,见配套源码
}

Worker 脚本:workers/PrimeWorker.ets

import { worker, MessageEvents } from '@kit.ArkTS'

// workerPort 是 Worker 侧的消息端口
const ctx = worker.workerPort

ctx.onmessage = (event: MessageEvents) => {
  const data = event.data as Record<string, number>
  const limit = data['limit'] as number

  const sieve: boolean[] = new Array<boolean>(limit + 1).fill(true)
  sieve[0] = false
  if (limit >= 1) { sieve[1] = false }

  const step = Math.floor(limit / 5)

  for (let i = 2; i * i <= limit; i++) {
    if (sieve[i]) {
      for (let j = i * i; j <= limit; j += i) {
        sieve[j] = false
      }
    }
    // 每 step 步上报一次进度给主线程
    if (i % step === 0) {
      ctx.postMessage({ type: 'progress', current: i })
    }
  }

  let count = 0
  for (let k = 0; k <= limit; k++) {
    if (sieve[k]) { count++ }
  }

  ctx.postMessage({ type: 'done', count: count })
}

模拟器运行效果

初始状态

Worker 初始状态


执行完成(含通信日志)

Worker 执行完成,找到 25997 个质数(300000 以内),耗时 53ms。通信日志清晰展示了「主线程创建→发送任务→收到结果」全链路。

Worker 执行完成,通信日志清晰可见


关键知识点

1. Worker 文件路径规则

// ✅ 正确:路径从 entry/ets/ 开始,写相对路径
new worker.ThreadWorker('entry/ets/workers/PrimeWorker.ets')

// ❌ 错误:不能写绝对路径或 ./ 相对路径
new worker.ThreadWorker('./workers/PrimeWorker.ets')  // 报错

2. 消息序列化限制

postMessage 传递的数据会被结构化克隆(Structured Clone),支持:

  • 基础类型:number、string、boolean
  • 普通对象:Record<string, ...>
  • 数组:number[]string[]

不支持(会抛出错误):

  • 函数、Symbol
  • DOM/UI 对象
  • 带方法的类实例(只传数据,不传行为)

3. 必须显式 terminate

Worker 线程不会自动销毁。忘记调用 terminate() 会导致:

  • Worker 进程驻留内存
  • 多次创建 Worker 导致内存泄漏
  • 影响设备续航(Worker 持续占用 CPU 时间片)
// ✅ 正确的清理模式
this.workerInstance?.terminate()
this.workerInstance = null

4. 双向通信设计模式

type 字段区分消息类型(类似枚举的消息协议):

// Worker 侧
ctx.postMessage({ type: 'progress', current: i })  // 进度
ctx.postMessage({ type: 'done', count: count })     // 完成

// 主线程侧
if (msgType === 'progress') { /* 更新进度 UI */ }
else if (msgType === 'done') { /* 展示结果 */ }

常见问题

Q:Worker 能访问 UI 组件吗?
A:不能。Worker 运行在独立 JS 引擎,完全没有 UI 上下文。所有 UI 更新必须在主线程的 onmessage 回调中完成。

Q:Worker 里能用 fetch 发网络请求吗?
A:HarmonyOS 的 Worker 支持使用 @ohos.net.http,但需要在 Worker 侧单独 import。Worker 有完整的运行时环境,可以使用大多数系统 API。

Q:应用退到后台,Worker 还在跑吗?
A:Worker 跟随应用进程生命周期。应用进入后台后,系统可能限制 Worker 的 CPU 时间;应用被杀死,Worker 同时销毁。如需真正后台执行,应使用 BackgroundTaskManager

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐