HarmonyOS 并发编程实战:TaskPool 与 Worker 的选型、通信与避坑
·
HarmonyOS 并发编程实战:TaskPool 与 Worker 的选型、通信与避坑
前言
HarmonyOS 主线程(UI 线程)负责渲染与交互,一旦执行耗时计算(大图解码、JSON 批量解析、加密运算、文件压缩)就会掉帧、卡 UI。ArkTS 并发模型提供两条主力路径:TaskPool(任务池) 与 Worker(长期工作线程)。两者都是基于 Actor 模型的「线程 + 消息通信」,但适用场景截然不同。本文用可运行示例讲清:什么时候用 TaskPool、什么时候用 Worker、线程间怎么传数据、以及最常见的「序列化失败」「上下文丢失」等坑。
问题描述
开发者常踩的坑:
- 把一个本来只需跑几百毫秒的计算丢进 Worker,结果「建线程的开销比计算还大」。
- 想给 Worker 传一个类实例,结果收到的是
undefined——因为 ArkTS 线程间只传可序列化数据。 - 在 TaskPool 任务里调用了 UI 相关 API(如
promptAction),直接抛异常。 - 忘记
worker.terminate(),后台堆积十几个 Worker 拖垮内存。
核心要分清:TaskPool 适合「一次性、无状态、可取消的短任务」;Worker 适合「长生命周期、有状态、需常驻的会话」。
细节解析
1. 两种模型对比
| 维度 | TaskPool | Worker |
|---|---|---|
| 生命周期 | 任务结束即回收,自动复用线程池 | 需手动 terminate() |
| 状态 | 无状态,不能持有全局变量 | 可维护自身状态 |
| 任务数量 | 理论无上限(自动调度) | 最多 8 个(含主线程限制) |
| 取消 | 支持 taskpool.cancel() | 需自行实现退出信号 |
| 典型场景 | 图片处理、加解密、排序 | 长连监听、持续采集、音视频 |
2. 线程间通信的数据约束
- 只能传可序列化对象:基础类型、Array、TypedArray、
ArrayBuffer(需transferList转移所有权)、实现了collections容器的对象等。 - 函数、
@ObservedV2实例、包含方法的类实例不能直接传。需要传「数据」而非「行为」。
3. ArrayBuffer 的所有权转移
通过 taskpool.Task 的第三个参数 transferList 把 ArrayBuffer 的所有权转移给工作线程,避免拷贝大内存,但转移后主线程那份立即失效。
4. 全局对象限制
Worker / TaskPool 环境没有 UI 上下文,不能调用 window、document、promptAction 等;也不能访问主线程的模块级单例。
示例代码(可运行 ArkTS/ArkUI)
示例 1:TaskPool 执行一个耗时计算
// utils/heavy.ets —— 注意:这是工作线程侧代码,不要引用 UI 模块
import { taskpool } from '@kit.ArkTS';
@Concurrent
function fibHeavy(n: number): number {
// 模拟耗时递归
if (n < 2) return n;
return fibHeavy(n - 1) + fibHeavy(n - 2);
}
// 主线程侧调用
async function runByTaskPool(): Promise<number> {
const task = new taskpool.Task(fibHeavy, 38);
return await taskpool.execute(task) as number;
}
// pages/Home.ets
import { runByTaskPool } from '../utils/heavy';
@Entry
@ComponentV2
struct Home {
@Local result: number = 0;
@Local loading: boolean = false;
build() {
Column({ space: 16 }) {
Button(this.loading ? '计算中…' : 'TaskPool 计算')
.enabled(!this.loading)
.onClick(async () => {
this.loading = true;
// 放到后台线程,UI 不卡
this.result = await runByTaskPool();
this.loading = false;
})
Text(`结果:${this.result}`).fontSize(18)
}
.padding(20)
}
}
示例 2:可取消的任务
import { taskpool } from '@kit.ArkTS';
@Concurrent
function countTo(n: number, progress: taskpool.TaskCallBack): void {
for (let i = 0; i < n; i++) {
progress.updateProgress?(i / n);
}
}
async function runCancellable() {
const task = new taskpool.Task(countTo, 1_000_000);
const exec = taskpool.execute(task, taskpool.Priority.HIGH);
// 中途取消
setTimeout(() => taskpool.cancel(task), 50);
try {
await exec;
} catch (e) {
console.info('任务已取消:', e);
}
}
示例 3:Worker 长生命周期会话
// workers/keepAlive.ets —— Worker 脚本
import worker from '@ohos.worker.worker';
const parent = worker.workerPort;
parent.onmessage = (e: MessageEvents) => {
const { type, payload } = e.data;
if (type === 'echo') {
// 工作线程可维护自己的状态
parent.postMessage({ type: 'result', payload: payload * 2 });
}
};
// 主线程
import { worker } from '@kit.ArkTS';
@Entry
@ComponentV2
struct WorkerDemo {
private wk: worker.ThreadWorker | null = null;
aboutToAppear() {
this.wk = new worker.ThreadWorker('entry/ets/workers/keepAlive.ets');
this.wk.onmessage = (e) => { console.info('来自 Worker:', JSON.stringify(e.data)); };
this.wk.postMessage({ type: 'echo', payload: 21 });
}
aboutToDisappear() {
this.wk?.terminate(); // 务必释放
this.wk = null;
}
build() { Column() { Text('Worker 示例') } }
}
总结
- 短、碎、可取消 → TaskPool(
@Concurrent函数 +taskpool.execute),零管理成本。 - 长、有状态、需常驻 → Worker,但记得
terminate(),且数量不超过 8 个。 - 传数据不传行为:线程间只传可序列化对象,函数/类实例要靠「主线程发数据 → 工作线程执行内置逻辑」的模式。
- 大内存用
transferList转移所有权,避免拷贝;转移后原线程那份失效。 - 工作线程无 UI 上下文,任何 UI API 调用都会抛异常,把计算留在后台、把结果回传主线程渲染。
合理并发化后,原本 300ms 的卡顿可以完全从主线程卸载,用户感知到的就是「丝滑」。
更多推荐



所有评论(0)