HarmonyOS 并发编程:TaskPool 任务池完全指南


一、引言
在现代应用开发中,性能是用户体验的核心。当应用需要处理计算密集型任务(如大数据计算、图像处理、加密解密)时,如果都在主线程执行,会导致界面卡顿、无响应。HarmonyOS 提供了强大的并发编程能力,其中 TaskPool(任务池) 是最常用的并发执行方案。
本文将以一个青绿网格风格的并发编程演示页面为主线,深入讲解 TaskPool 的使用方法、底层原理和最佳实践,帮助读者掌握并发编程的核心技能。
二、为什么需要并发
2.1 主线程的职责
HarmonyOS 应用中,主线程(UI 线程)负责处理 UI 渲染和事件响应。主线程的职责包括:
- 处理用户交互事件(点击、滑动、输入)。
- 执行 UI 渲染。
- 处理系统消息。
如果主线程被耗时任务阻塞,用户会感觉到界面卡顿、无响应,甚至触发系统"应用无响应"(ANR)提示。
2.2 耗时任务的危害
以下任务如果放在主线程执行,会造成严重卡顿:
- 大量数据计算(如 1000 万次循环累加)。
- 图片解码和缩放。
- JSON 大数据解析。
- 文件读写。
- 网络请求(应使用异步 API)。
2.3 并发方案对比
HarmonyOS 提供两种主要的并发方案:
| 方案 | 特点 | 适用场景 |
|---|---|---|
| TaskPool | 线程池复用,自动调度,任务级并发 | 计算密集型任务 |
| Worker | 独立线程,长生命周期,消息通信 | 长耗时任务、常驻任务 |
三、TaskPool 核心概念
3.1 什么是 TaskPool
TaskPool(任务池)是 HarmonyOS 提供的一种任务级并发执行框架。它的核心思想是:
- 维护一个线程池,复用线程资源。
- 开发者提交任务(函数),框架自动调度到空闲线程执行。
- 任务执行完成后,结果返回给调用方。
3.2 核心 API
| API | 说明 |
|---|---|
taskpool.execute() |
提交单个任务 |
taskpool.executeDelayed() |
延迟提交任务 |
taskpool.TaskGroup |
任务组,批量执行 |
@Concurrent |
标记可并行执行的函数 |
3.3 @Concurrent 装饰器
@Concurrent 是 TaskPool 的关键装饰器,用于标记可以被 TaskPool 执行的函数:
@Concurrent
function computeSum(n: number): number {
// 计算密集型逻辑
let sum = 0;
for (let i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
代码说明:
@Concurrent装饰的函数可以提交到 TaskPool 执行。- 被装饰的函数有以下限制:
- 只能使用普通参数和返回值,不能使用闭包变量。
- 不能访问 UI 相关对象。
- 参数和返回值必须可序列化(支持基本类型、ArrayBuffer、可序列化对象等)。
四、实战代码:并发计算页面
下面我们实现一个演示 TaskPool 并发计算的页面。页面采用青绿网格风格,包含 4 个可点击的任务卡片。
4.1 定义数据结构
interface TaskRow {
name: string;
status: string;
time: string;
}
代码说明:
TaskRow 接口描述任务表格中的一行数据:
name:任务名称。status:任务状态(空闲/运行中/完成/失败)。time:任务耗时。
4.2 定义并发任务函数
// 计算密集型任务:必须使用 @Concurrent 导出
@Concurrent
function computeSum(n: number): number {
let sum = 0;
for (let i = 1; i <= n; i++) {
sum += i;
}
return sum;
}
代码说明:
computeSum 是一个计算密集型任务函数:
-
@Concurrent 装饰:标记该函数可以提交到 TaskPool 执行。
-
函数逻辑:计算从 1 到 n 的累加和。当 n 很大(如 1000 万)时,这是一个耗时操作,适合放到后台线程执行。
-
参数和返回值:参数
n是 number,返回值是 number,都是可序列化的基本类型,满足 TaskPool 的要求。 -
为什么用 @Concurrent:没有
@Concurrent装饰的函数无法直接提交到 TaskPool。这个装饰器告诉框架该函数是线程安全的、可以在独立线程执行的。
4.3 组件状态定义
@Entry
@Component
struct ConcurrencyPage {
@State tasks: TaskRow[] = [
{ name: '任务 A', status: '空闲', time: '-' },
{ name: '任务 B', status: '空闲', time: '-' },
{ name: '任务 C', status: '空闲', time: '-' },
{ name: '任务 D', status: '空闲', time: '-' }
];
@State running: boolean = false;
代码说明:
@State tasks:任务列表,初始状态全部为空闲。@State running:是否正在执行任务的标志,用于防止重复提交。
4.4 提交任务方法
async runTask(index: number): Promise<void> {
if (this.running) {
return;
}
this.running = true;
this.tasks[index] = { name: `任务 ${String.fromCharCode(65 + index)}`, status: '运行中', time: '-' };
const start = Date.now();
try {
// 提交任务到 TaskPool
const result: number = await taskpool.execute(computeSum, [10000000]);
const elapsed = ((Date.now() - start) / 1000).toFixed(2);
this.tasks[index] = { name: `任务 ${String.fromCharCode(65 + index)}`, status: `完成 Σ=${result}`, time: `${elapsed}s` };
} catch (e) {
this.tasks[index] = { name: `任务 ${String.fromCharCode(65 + index)}`, status: '失败', time: '-' };
} finally {
this.running = false;
}
}
代码说明:
runTask 方法是 TaskPool 使用的核心:
-
防重入:
if (this.running) return防止重复提交任务。 -
更新状态:将任务状态更新为"运行中",由于
tasks是@State数组,UI 会自动刷新。 -
提交任务:
taskpool.execute(computeSum, [10000000])是核心调用:- 第一个参数:要执行的函数(必须是
@Concurrent装饰的)。 - 第二个参数:函数参数数组,
[10000000]表示传入n = 10000000。 - 返回 Promise,
await等待任务完成。
- 第一个参数:要执行的函数(必须是
-
获取结果:任务完成后,返回值
result就是computeSum的计算结果(1 到 1000 万的和)。 -
计算耗时:用
Date.now()记录开始和结束时间,计算任务耗时。 -
更新完成状态:将任务状态更新为"完成",并显示计算结果和耗时。
-
异常处理:
catch块处理任务执行异常,将状态更新为"失败"。 -
恢复标志:
finally块中恢复running = false,允许下次提交。
4.5 并行执行全部任务
Button(this.running ? '计算中...' : '并行执行全部任务')
.width('100%')
.height(50)
.fontSize(15)
.fontWeight(FontWeight.Bold)
.fontColor('#0D2818')
.backgroundColor('#7CFC00')
.borderRadius(0)
.onClick(() => {
if (this.running) {
return;
}
for (let i = 0; i < 4; i++) {
this.runTask(i);
}
})
代码说明:
"并行执行全部任务"按钮:
- 点击后通过
for循环连续调用runTask四次,同时提交 4 个任务。 - 由于
runTask是异步方法,4 个taskpool.execute调用会几乎同时提交,由 TaskPool 的线程池并行调度执行。 - 这正是 TaskPool 的价值:无需手动管理线程,框架自动并行调度。
4.6 构建 UI
build() {
Scroll() {
Column({ space: 16 }) {
// 顶部青绿标题
Column() {
Text('CONCURRENCY')
.fontSize(12)
.fontColor('#7CFC00')
.letterSpacing(4)
Text('并发编程')
.fontSize(26)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
.margin({ top: 6 })
Text('TaskPool 任务池 · @Concurrent')
.fontSize(12)
.fontColor('#7CFC00')
.margin({ top: 6 })
}
.width('100%')
.padding({ top: 48, bottom: 30 })
.backgroundColor('#0D2818')
// Grid 网格任务区
Grid() {
ForEach(this.tasks, (task: TaskRow, index: number) => {
GridItem() {
Column({ space: 8 }) {
Text(task.name)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor('#7CFC00')
Text(task.status)
.fontSize(11)
.fontColor(task.status === '空闲' ? '#88A088' : Color.White)
.textAlign(TextAlign.Center)
Text(task.time)
.fontSize(11)
.fontColor('#7CFC00')
}
.width('100%')
.height(100)
.justifyContent(FlexAlign.Center)
.backgroundColor('#122A1A')
.borderRadius(8)
.border({ width: 1, color: '#7CFC0044' })
.onClick(() => { this.runTask(index); })
}
})
}
.columnsTemplate('1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width('100%')
.height(220)
代码说明:
任务区使用 Grid 网格布局:
-
Grid 布局:
.columnsTemplate('1fr 1fr')设置两列等宽,.columnsGap(10)和.rowsGap(10)设置行列间距。 -
任务卡片:每个
GridItem包含任务名称、状态、耗时三个信息。 -
状态颜色:空闲状态使用灰色文字,运行中/完成状态使用白色文字,通过条件判断实现。
-
点击执行:点击卡片调用
runTask(index)执行对应任务。 -
网格风格:直角边框、青绿配色,与页面整体风格统一。
五、TaskGroup 任务组
当需要批量执行多个任务并统一等待结果时,可以使用 TaskGroup:
async function runGroup(): Promise<void> {
// 创建任务组
const group = new taskpool.TaskGroup();
// 添加任务
group.addTask(computeSum, [10000000]);
group.addTask(computeSum, [20000000]);
group.addTask(computeSum, [30000000]);
// 执行任务组,等待所有任务完成
const results: number[] = await taskpool.execute(group);
// 处理结果
results.forEach((result: number, index: number) => {
console.info(`任务 ${index} 结果: ${result}`);
});
}
代码说明:
taskpool.TaskGroup创建任务组对象。group.addTask(func, args)向任务组添加任务。taskpool.execute(group)执行整个任务组,返回所有任务结果的数组。- 任务组适合"多个任务并行执行,全部完成后统一处理"的场景。
六、Task 对象与优先级
除了直接传函数,还可以创建 Task 对象并设置优先级:
// 创建任务对象
const task = new taskpool.Task(computeSum, [5000000]);
// 设置任务优先级
task.priority = taskpool.Priority.HIGH;
// 执行
const result = await taskpool.execute(task);
代码说明:
taskpool.Task创建任务对象,可以携带优先级等元信息。taskpool.Priority枚举包含LOW、MEDIUM、HIGH三个优先级。- 高优先级任务会被优先调度执行。
七、TaskPool 与 Worker 的选择
| 特性 | TaskPool | Worker |
|---|---|---|
| 生命周期 | 任务级,短 | 线程级,长 |
| 线程管理 | 自动复用 | 手动创建 |
| 通信方式 | 函数调用 | 消息传递 |
| 适用场景 | 计算密集、一次性任务 | 常驻任务、持续通信 |
| 使用复杂度 | 简单 | 较复杂 |
选择建议:
- 一次性计算任务 → TaskPool。
- 需要常驻后台持续处理(如实时数据处理)→ Worker。
- 需要与主线程频繁双向通信 → Worker。
八、最佳实践
8.1 合理划分任务粒度
任务粒度过小会导致线程调度开销大于计算收益,粒度过大则无法充分利用多核。一般建议单个任务执行时间在毫秒到秒级。
8.2 避免共享可变状态
TaskPool 任务之间是隔离的,尽量避免共享可变状态。如果需要共享数据,使用 ArrayBuffer 或可序列化对象传递。
8.3 正确处理异步
taskpool.execute 返回 Promise,务必使用 await 或 .then() 处理,避免未捕获异常。
8.4 控制并发数量
不要无限制提交任务,应根据任务量合理控制并发,避免线程池饱和。
九、常见问题
9.1 任务函数无法提交
原因:函数没有使用 @Concurrent 装饰,或函数使用了闭包变量。
解决:为函数添加 @Concurrent 装饰器,确保不使用闭包变量。
9.2 任务参数无法序列化
原因:参数包含不可序列化的对象(如 UI 组件、函数)。
解决:只传递可序列化的数据(基本类型、ArrayBuffer、JSON 对象)。
9.3 主线程卡顿
原因:耗时任务没有放到 TaskPool 执行,阻塞了主线程。
解决:将计算密集型任务提交到 TaskPool。
十、总结
本文深入讲解了 HarmonyOS TaskPool 并发编程,通过一个青绿网格风格的演示页面实战演示了任务提交、并行执行、结果获取等核心能力。
核心要点回顾:
- 主线程负责 UI,耗时任务必须放到后台线程。
@Concurrent标记可并行执行的函数。taskpool.execute()提交任务,返回 Promise。TaskGroup批量执行任务并统一等待。Task对象支持设置优先级。- TaskPool 适合一次性计算任务,Worker 适合常驻任务。
- 避免共享可变状态,正确处理异步。
并发编程是提升应用性能的关键技术,掌握它能让应用在多核设备上发挥最大性能。下一篇我们将讲解 HarmonyOS 路由导航。
更多推荐



所有评论(0)