摘要: 主线程做图片解码卡 UI、JSON 解析大文件卡顿、数据库写入阻塞——重活必须切工作线程。鸿蒙提供 TaskPool 和 Worker 两套并发方案,选错性能差 3 倍。本文用图片解码/JSON 解析/数据库写入为案例,讲清选型与 5 个踩坑。

适用版本: HarmonyOS NEXT 7.x / API 14+(2026 年稳定版)

开篇:主线程卡住的那三秒

“列表滚动卡顿,因为图片在主线程解码。”

2026 年 8 月,涟漪睡眠 App 列表滚动掉帧。Profiler 显示主线程在 decodeImage。图片解码、JSON 解析、数据库写入这些重活必须切工作线程。鸿蒙有 TaskPool 和 Worker 两套方案,我选错了性能差 3 倍。这篇把选型和踩坑讲透。

图:TaskPool vs Worker 性能对比 + @Concurrent 函数使用示例

一、TaskPool vs Worker

多线程并发实战——TaskPool 与 Worker 选型与踩坑(HarmonyOS 7.x)|图 2

多线程并发实战——TaskPool 与 Worker 选型与踩坑(HarmonyOS 7.x)|图 1

维度TaskPoolWorker
线程管理自动池化手动创建销毁
任务调度自动复用固定线程
通信返回值postMessage
适合短任务/大量长任务/有状态
性能高(池化复用)中(创建开销)

二、TaskPool 实战

图片解码切工作线程

import { taskpool } from '@kit.ArkTS';
import { image } from '@kit.ImageKit';
import { http } from '@kit.NetworkKit';

@Concurrent
function decodeImage(buffer: ArrayBuffer): image.PixelMap {
  return image.createImageSource(buffer).createPixelMapSync();
}

// 主线程:先下载字节流(ArkTS 没有浏览器那套 fetch,用 NetworkKit 的 http),
// 再切工作线程解码
async function loadImage(url: string): Promise<image.PixelMap> {
  const buffer = await downloadBytes(url);
  return await taskpool.execute(decodeImage, buffer) as image.PixelMap;  // 切工作线程
}

async function downloadBytes(url: string): Promise<ArrayBuffer> {
  const request = http.createHttp();
  try {
    const resp = await request.request(url, {
      method: http.RequestMethod.GET,
      expectDataType: http.HttpDataType.ARRAY_BUFFER,
    });
    return resp.result as ArrayBuffer;
  } finally {
    request.destroy();
  }
}

JSON 大文件解析

import { taskpool } from '@kit.ArkTS';
import { http } from '@kit.NetworkKit';

// Data 为业务模型,示例字段:interface Data { items: Array<Record<string, string>> }
@Concurrent
function parseLargeJson(text: string): object {
  return JSON.parse(text);  // 大文件解析不卡主线程
}

async function loadData(): Promise<Data> {
  const request = http.createHttp();
  let text = '';
  try {
    const resp = await request.request('https://example.com/large.json', {
      method: http.RequestMethod.GET,
      expectDataType: http.HttpDataType.STRING,
    });
    text = resp.result as string;
  } finally {
    request.destroy();
  }
  return await taskpool.execute(parseLargeJson, text) as Data;
}

避坑 1:@Concurrent 函数里不能访问主线程对象(this、@State 变量等),所有数据通过参数传入,返回值通过 await 接收。


三、Worker 实战

长任务(如持续数据处理)

// —— DataWorker.ets(工作线程侧)——
import { worker } from '@kit.ArkTS';

const workerPort = worker.workerPort;
workerPort.onmessage = (e: MessageEvents) => {
  const data = e.data;
  // 处理数据
  const result = process(data);
  workerPort.postMessage(result);
};

// —— 主线程侧(写在页面组件的成员方法里,如 aboutToAppear)——
// 注意:两侧是两个文件,上面的 import 必须在 DataWorker.ets 顶部
const myWorker = new worker.ThreadWorker('entry/ets/workers/DataWorker.ets');
myWorker.onmessage = (e: MessageEvents) => {
  this.updateUI(e.data);
};
myWorker.postMessage({ action: 'process', data: rawData });
this.myWorker = myWorker;  // 存为组件成员,aboutToDisappear 时销毁(见踩坑 5)

避坑 2:Worker 创建有开销(~50ms),不要频繁创建销毁。长生命周期任务用 Worker,短任务用 TaskPool。

实测数据(涟漪睡眠 App,Mate 60 Pro,HarmonyOS 7.x)

指标优化前(主线程)优化后(TaskPool)
列表滚动平均帧耗时22ms(频繁掉帧)8ms(满帧)
100 张图 1024px 解码总耗时4.1s(阻塞 UI)0.9s(8 线程池)
5MB JSON 解析380ms(主线程卡死)45ms(工作线程)
Worker 创建开销—~50ms/次

四、5 个并发踩坑

1. @Concurrent 函数访问了主线程对象

// ❌ 错误:访问 this
@Concurrent
function bad(): void {
  this.count++;  // this 是主线程对象,报错
}

// ✅ 正确:通过参数传
@Concurrent
function good(count: number): number {
  return count + 1;
}

2. TaskPool 任务太大

现象:把整个业务逻辑塞进一个 @Concurrent 函数,执行 5 秒,阻塞线程池。

解决:拆分小任务,TaskPool 适合短任务(<500ms),长任务用 Worker。

3. Worker 通信数据序列化开销

现象:postMessage 传 10MB 数据,序列化耗时 200ms。

解决:用 Transferable 转移所有权(零拷贝):

// 转移 ArrayBuffer 所有权,myWorker 为 aboutToAppear 里创建的组件成员
this.myWorker.postMessage({ buffer: largeBuffer }, [largeBuffer]);

4. 并发更新同一状态

现象:多个 TaskPool 任务并发更新 @State,状态错乱。

解决:并发任务只返回结果,主线程统一更新状态:

// 写在组件成员方法里
import { taskpool } from '@kit.ArkTS';

const results = await Promise.all([
  taskpool.execute(task1),
  taskpool.execute(task2),
]);
this.combinedResult = mergeResults(results);  // 主线程统一更新

5. Worker 没销毁

现象:页面退出后 Worker 还在跑,内存泄漏。

解决:aboutToDisappear 销毁:

// myWorker 是组件成员变量,必须在 aboutToAppear 里创建,与 aboutToDisappear 成对
aboutToDisappear() {
  this.myWorker.terminate();
}

五、选型决策

<500ms 短任务

>500ms 长任务

否 无状态

是 有状态

耗时任务

任务时长?

TaskPool
自动池化

需要状态?

Worker
独立线程

多线程并发实战——TaskPool 与 Worker 选型与踩坑(HarmonyOS 7.x)|图 3


避坑要点

  1. @Concurrent 不访问主线程对象:数据通过参数传
  2. 短任务用 TaskPool:自动池化复用,性能高
  3. Worker 要 terminate:不销毁内存泄漏

边界与已知限制

限制项具体表现规避方式
可序列化@Concurrent 函数只能接收可序列化参数,函数、含方法的类实例传不进去只传基础类型/Plain Object,在子线程内重建对象
闭包禁止函数体内不能引用外部变量与 this全部通过参数传入,结果通过返回值带出
执行时长单次任务建议 3 分钟内,超时有被系统回收的风险长任务改 Worker,并拆分成可续跑的片段
线程数量TaskPool 并发度随设备核数浮动,不能假定固定线程数;Worker 同时存活数有上限用任务队列控制并发,创建 Worker 时捕获上限异常
UI 操作子线程无法更新 UI结果回主线程后再改状态
内存转移ArrayBuffer 传递是转移语义,传出后原线程不可再访问需要双向访问时用拷贝而非转移
调试能力子线程断点支持有限以 HiLog 打点为主,关键路径留日志

小结

选型一句话:短、纯计算、可序列化的任务 → TaskPool;长驻、长时、需要手动控制生命周期的 → Worker。

真正卡住迁移的往往不是并发 API 本身,而是"可序列化"这一关:把函数内部依赖的外部变量全部改成显式参数,迁移动作就完成了一大半。剩下的坑集中在生命周期——Worker 用完必须 terminate(),否则内存只涨不降。

评论区交流: 你的并发方案选 TaskPool 还是 Worker?评论区交流。

Logo

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

更多推荐