一、背景说明

HarmonyOS 应用开发中,耗时操作(网络请求、JSON 大文件解析、图片压缩等)如果直接放在主线程执行,会阻塞 UI 渲染,造成页面卡顿甚至冻屏告警。ArkTS 提供了 TaskPool 和 Worker 两种多线程并发方案,其中 TaskPool 是官方推荐的轻量级方案:任务自动调度到任务池线程执行,支持优先级设置和线程自动回收,无需开发者手动管理线程生命周期。随着 OpenHarmony 7.0 Release 正式版发布(配套 API 版本持续演进),TaskPool 的能力也在不断增强,掌握它是写出高性能鸿蒙应用的必备技能。

二、技术原理

TaskPool 的核心机制是一个系统维护的任务线程池。调用 taskpool.execute() 时,系统将传入的任务函数及其参数序列化后分发给空闲的工作线程执行,执行完毕后将结果返回给宿主线程,整体流程如下:

宿主线程                          TaskPool 工作线程
   |                                    |
   |-- taskpool.execute(task, args) --> |
   |          (序列化传输参数)          |-- 执行任务函数
   |                                    |
   | <-- 返回结果 / 抛出异常 ----------- |

几个关键概念:

  1. 任务函数必须是 @Concurrent 修饰的函数:@Concurrent 告知编译器该函数将在非宿主线程执行,函数及其引用的变量需要满足可序列化要求。
  2. 参数与返回值按值序列化传递:普通对象、数组、基础类型可直接传递;需要在并发实例间共享引用的对象需使用 @Sendable 装饰。
  3. 优先级:taskpool.execute() 可传入 taskpool.Priority(HIGH / MEDIUM / LOW),新版本还扩展了 VIP、DEADLINE 等优先级,让关键任务优先获得线程资源。
  4. Task 对象:对于需要设置任务名、取消、依赖关系的场景,可先创建 taskpool.Task 实例再执行。

TaskPool 与 Worker 的取舍:TaskPool 适合短时、无生命周期管理的并发任务(几秒内的计算),系统自动负载均衡;Worker 适合长时间驻留的后台线程(如常驻的音频解码、定时轮询),需要手动创建和销毁。绝大多数场景用 TaskPool 即可。

三、实现步骤

以一个"后台解析大量 JSON 数据并统计"的完整工程为例。

  1. 新建工程后无需申请任何权限(纯本地计算),如需在任务中发起网络请求,在 entry/src/main/module.json5 中添加:
{
  "module": {
    "name": "entry",
    "requestPermissions": [
      { "name": "ohos.permission.INTERNET" }
    ],
    "abilities": [
      {
        "name": "EntryAbility",
        "srcEntry": "./ets/entryability/EntryAbility.ets",
        "exported": true,
        "startWindowIcon": "$media:icon",
        "startWindowBackground": "$color:start_window_background",
        "skills": [{ "entities": ["entity.system.home"], "actions": ["action.system.home"] }]
      }
    ]
  }
}
  1. 新建任务文件 entry/src/main/ets/tasks/parseTask.ets,定义 @Concurrent 任务函数:
// parseTask.ets
import { taskpool } from '@kit.ArkTS';

// 定义一个可序列化的数据结构
export interface RawRecord {
  id: number;
  name: string;
  score: number;
}

// @Concurrent 任务:统计各记录得分,返回汇总结果
// 注意:@Concurrent 函数只能引用模块级变量和导入的模块,不能访问宿主线程的 this
@Concurrent
function parseAndSum(records: RawRecord[]): number {
  let total: number = 0;
  for (let i = 0; i < records.length; i++) {
    total += records[i].score;
  }
  return total;
}

// 对外封装:把任务抛到 TaskPool 执行并返回 Promise
export function sumScores(records: RawRecord[]): Promise<number> {
  const task: taskpool.Task = new taskpool.Task(parseAndSum, records);
  task.name = 'sumScoresTask'; // 任务名,便于日志追踪
  return taskpool.execute(task, taskpool.Priority.MEDIUM) as Promise<number>;
}
  1. UI 页面 entry/src/main/ets/pages/Index.ets 调用示例,点击按钮后后台执行任务,结果刷新到界面:
// Index.ets
import { promptAction } from '@kit.ArkUI';
import { sumScores, RawRecord } from '../tasks/parseTask';

@Entry
@Component
struct Index {
  @State result: string = '点击按钮开始统计';
  @State running: boolean = false;

  // 构造 10 万条模拟数据
  private makeData(): RawRecord[] {
    const list: RawRecord[] = [];
    for (let i = 0; i < 100000; i++) {
      list.push({ id: i, name: `user_${i}`, score: i % 100 });
    }
    return list;
  }

  private startTask(): void {
    if (this.running) {
      return; // 防止重复提交
    }
    this.running = true;
    this.result = '计算中...';
    sumScores(this.makeData())
      .then((total: number) => {
        this.result = `总分:${total}`;
        promptAction.showToast({ message: '统计完成' });
      })
      .catch((err: Error) => {
        this.result = `任务失败:${err.message}`;
      })
      .finally(() => {
        this.running = false;
      });
  }

  build() {
    Column({ space: 20 }) {
      Text(this.result)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      Button(this.running ? '执行中' : '开始统计')
        .width(200)
        .enabled(!this.running)
        .onClick(() => this.startTask())
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}
  1. 运行后点击按钮,10 万条数据求和在后台线程瞬间完成,UI 全程不卡顿。
四、应用场景示例

图片压缩是典型的耗时任务场景,同样可以用 TaskPool 承载:

// compressTask.ets
import { taskpool } from '@kit.ArkTS';
import { image } from '@kit.ImageKit';

@Concurrent
async function compressPixelMap(pm: image.PixelMap, w: number, h: number): Promise<image.PixelMap> {
  // 在工作线程完成重采样,主线程只负责展示
  return pm.createPixelMap({ width: w, height: h })
    .then(() => pm.scale(w, h))
    .then(() => pm);
}

export function compressInPool(pm: image.PixelMap, w: number, h: number): Promise<image.PixelMap> {
  return taskpool.execute(compressPixelMap, pm, w, h) as Promise<image.PixelMap>;
}
// 页面中调用:列表缩略图批量压缩
compressInPool(originPixelMap, 200, 200)
  .then((thumb: image.PixelMap) => this.thumbnails.push(thumb))
  .catch((err: Error) => console.error(`压缩失败: ${err.message}`));
五、技术要点
  1. @Concurrent 的限制:任务函数内部不能访问宿主线程的 this 与 UI 上下文;闭包捕获的变量也受序列化约束,需要共享状态时改用 @Sendable 类。
  2. 优先级与取消:交互触发的任务用 taskpool.Priority.HIGH,预加载类任务用 LOW;长时间任务应支持取消,避免浪费线程资源:
const task: taskpool.Task = new taskpool.Task(longRunJob, input);
const promise: Promise<string> = taskpool.execute(task) as Promise<string>;
// 需要取消时
task.cancel(); // 任务未开始执行会直接取消,已开始则抛出 BusinessError(取消码)
  1. 错误处理:execute() 返回的 Promise 会把工作线程内抛出的异常透传到宿主线程,用 catch 捕获并按 BusinessError 的 code 分类处理:
import { BusinessError } from '@kit.BasicServicesKit';

taskpool.execute(parseAndSum, data).catch((err: BusinessError) => {
  console.error(`taskpool failed, code=${err.code}, msg=${err.message}`);
});
  1. 长任务说明:TaskPool 工作线程默认 3 分钟无任务会被系统回收,单任务不宜长时间占线程;确需常驻线程(如持续解码流)请改用 Worker。
  2. 性能建议:任务粒度适中——太碎则序列化开销超过计算收益,太粗则无法并行。批量数据处理可切分后提交多个任务,让线程池自动并行。
六、扩展建议
  1. 组合异步:多个独立任务可用 Promise.all 汇聚,全部完成后再刷新 UI。
  2. 跨线程共享大对象:高频读写的大数组可定义为 @Sendable 类,配合 taskpool 实现引用传递,避免反复拷贝。
  3. 动态导入任务:任务较重时可使用动态 import 在任务内按需加载模块,进一步降低首屏开销。
@Concurrent
async function heavyJob(n: number): Promise<number> {
  const mod = await import('../utils/bigMath'); // 按需加载重模块
  return mod.fib(n);
}
结语

本文基于 ArkTS TaskPool 实现了完整的多线程并发处理工程,核心代码不到 100 行,就覆盖了任务定义、优先级、取消与错误处理的完整链路。相比手动管理 Worker,TaskPool 让"主线程不卡顿"成为几乎零成本的能力,建议所有涉及耗时操作的鸿蒙应用都优先采用这一方案。

Logo

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

更多推荐