一、TaskPool 的工作原理
- HarmonyOS Next 的 TaskPool 是一个专为应用提供多线程运行环境的系统级组件,帮助开发者高效处理复杂或耗时的任务。它的核心理念是让开发者从繁琐的线程管理中解放出来,只需专注于任务本身,由系统负责调度和优化,从而最大化系统利用率并改善用户体验。
- TaskPool 并非横空出世的全新技术,它是在 Worker 的基础上进行的高层抽象与封装。Worker 是一个功能完备的独立线程,但需要开发者手动管理其生命周期,而 TaskPool 在其之上构建了一套调度系统,实现了自动化管理。
- TaskPool 支持在宿主线程提交任务到任务队列,系统选择合适的工作线程执行任务,并将结果返回给宿主线程。接口易用,支持任务执行、取消和指定优先级。通过系统统一线程管理,结合动态调度和负载均衡算法,可以节约系统资源。系统默认启动一个任务工作线程,任务多时会自动扩容。工作线程数量上限由设备的物理核数决定,内部管理具体数量,确保调度和执行效率最优。长时间无任务分发时会缩容,减少工作线程数量。

- 如上所示,是官方 TaskPool 运行机制的示意图,TaskPool 并非独立的技术,它是在 Worker 的基础上进行的高层抽象与封装,Worker 本身是一个功能完备的独立线程,但需要开发者手动管理其生命周期,而 TaskPool 在其之上构建了一套调度系统,实现了自动化管理。它的工作流程可以概括为提交、调度、执行与返回四个步骤,其运作如下所示:

- 流程说明:
-
- 任务提交:开发者在主线程中,使用 execute() 方法将需要执行的任务提交给 TaskPool。
-
- 排队等待:提交的任务会被放入内部的任务队列。同时支持将多个任务放入一个 任务组 (TaskGroup) 并一起执行,并可以分别为任务或任务组指定优先级,高优先级的任务会被先执行。
-
- 调度分配:TaskPool 内部的调度器会根据任务和任务组的优先级,从队列中取出任务,分配给它所管理的 Worker 线程池中的一个空闲线程去执行。系统中Worker 的最大数量为 64 个。
-
- 返回结果:任务执行完成后,结果会通过 Promise 的形式异步返回给主线程。TaskPool 与 Worker 都支持普通对象、ArrayBuffer 等多种数据类型的传递。
二、TaskPool 的核心机制与特性
- TaskPool 的自动化特性主要体现在对线程资源的智能管理上:
-
- 生命周期管理:这是 TaskPool 的核心优势,线程的创建、销毁和复用都由系统全权负责,开发的时候只需关注业务逻辑。
-
- 自动扩缩容:TaskPool 会监控任务队列的长度,动态调整工作线程的数量。扩容:当任务增多时,系统会自动增加工作线程来应对高并发。缩容:当任务减少时,系统会回收空闲线程以节约资源。
-
- 优先级调度:通过为任务设置优先级,可以让重要的任务(如 UI 响应)优先执行,提升应用交互的流畅度。
-
- 任务取消:TaskPool 提供了取消正在执行或等待中任务的能力,这在用户快速滑动列表等场景下非常有用,能有效节省系统资源。
三、@Concurrent 装饰器
- 使用 TaskPool 时,执行的并发函数必须用该装饰器修饰,否则无法通过校验。@Concurrent 装饰器是理解和使用 TaskPool 的关键,它本质上是一个编译期检查和运行时标记,用于声明一个函数可以被安全地在并发环境中执行。
- 在 HarmonyOS 中,并非所有函数都能被提交给 TaskPool 执行。@Concurrent 装饰器的作用,就是将某个普通函数标记为"并发函数",允许 TaskPool 将其调度到后台线程中运行,而不会阻塞主线程。同时,编译器会依据 @Concurrent 的约束对函数进行严格的校验,如果不符合规则,将会在编译时报错。
- @Concurrent 为了确保线程安全和数据一致性,建立了一套明确的约束规则,总结如下:
| 约束类别 | 详细规则 |
|---|---|
| 适用范围 | @Concurrent 必须标记在普通函数或 async 函数上,不能标记在箭头函数、类成员函数、匿名函数、generator函数上,而且仅支持在 Stage 模型的 .ets 文件中使用 |
| 变量使用 | 禁止使用闭包变量,允许使用局部变量、参数变量和通过 import 引入的变量 |
| 数据传递 | 所有入参和返回值都必须是可序列化的类型 |
| 执行限制 | 同步任务的执行时长不能超过 3 分钟,异步任务(如 I/O 操作)的等待时间不计入时长 |
四、TaskPool 核心用法
① 基本用法与数据传递
- 使用 TaskPool 并传递数据给并发函数:
javascript
import { taskpool } from '@kit.ArkTS';
/**
* 1. 定义并发函数:该函数将在子线程中执行
* 必须使用 @Concurrent 装饰器标记
* 入参和返回值必须支持序列化
*/
@Concurrent
processData(data: number[]): number {
// 模拟一个耗时计算任务
let sum = 0;
for (let i = 0; i < data.length; i++) {
sum += data[i];
}
// 该子线程中的日志需要通过 hilog 查看
console.info(`[Worker Thread] Processing finished, sum is ${sum}`);
return sum;
}
/**
* 2. 主线程中提交任务并接收结果
*/
async runTaskInPool() {
const inputData = [1, 2, 3, 4, 5];
try {
// 提交任务,TaskPool 会自动将其分配给一个空闲的工作线程
const result = await taskpool.execute(processData, inputData);
// result 将返回计算后的总和
console.info(`[Main Thread] Task result: ${result}`);
} catch (error) {
console.error(`[Main Thread] Task failed: ${error.code} - ${error.message}`);
}
}
② 设置任务优先级
- 可以通过 execute 方法的第二个参数显式设置任务的优先级,以影响其调度顺序:
javascript
import { taskpool } from '@kit.ArkTS';
@Concurrent
importantTask(data: string): string {
// 执行重要业务逻辑
return `Processed: ${data}`;
}
async runWithPriority() {
const task = new taskpool.Task(importantTask, "Critical Data");
// 执行任务并设置为高优先级
const result = await taskpool.execute(task, taskpool.Priority.HIGH);
console.info(`High priority task result: ${result}`);
}
③ 取消正在等待的任务
- 对于已经提交但尚未开始执行的任务,可以将其取消:
javascript
import { taskpool } from '@kit.ArkTS';
@Concurrent
cancellableTask(iterations: number): number {
let sum = 0;
for (let i = 0; i < iterations; i++) {
sum += i;
}
return sum;
}
async cancelTaskDemo() {
const task = new taskpool.Task(cancellableTask, 100000000);
// 执行任务,并捕获可能的取消错误
taskpool.execute(task).catch((error: BusinessError) => {
if (error.code === taskpool.ErrorCode.TASK_CANCELED) {
console.info("Task was cancelled before execution.");
}
});
// 在任务开始前取消它
await taskpool.cancel(task);
}
④ 使用任务组 (TaskGroup)
- 当需要并行执行一组相关任务,并等待所有任务完成时,可以使用 TaskGroup:
javascript
import { taskpool } from '@kit.ArkTS';
@Concurrent
multiply(a: number, b: number): number {
return a * b;
}
async runTaskGroup() {
// 1. 创建任务组
const group = new taskpool.TaskGroup();
// 2. 将多个任务添加到组中
group.addTask(multiply, 2, 3); // 任务1: 2*3
group.addTask(multiply, 4, 5); // 任务2: 4*5
group.addTask(multiply, 6, 7); // 任务3: 6*7
// 3. 执行整个任务组,等待所有任务完成,结果将以数组形式返回
const results: number[] = await taskpool.execute(group);
console.info(`Task group results: ${results}`); // 输出: [6, 20, 42]
}
五、TaskPool 扩缩容机制
- 扩容机制:一般情况下,向任务队列提交任务时会触发扩容检测。扩容检测首先判断当前空闲工作线程数是否大于任务数。如果大于,说明线程池中有空闲工作线程,无需扩容。否则,通过负载计算确定所需工作线程数并创建。
- 扩容后,TaskPool 创建多个工作线程,但当任务数减少后,这些线程就会处于空闲状态,造成资源浪费,因此,TaskPool 提供了缩容机制。TaskPool 使用定时器,每 30 秒检测一次当前负载,并尝试释放空闲的工作线程。
- 释放的线程需满足以下条件:
-
- 该线程空闲时长达到 30s;
-
- 该线程上未执行长时任务(LongTask);
-
- 该线程上没有业务申请且未释放的句柄,例如 Timer(定时器);
-
- 该线程处于非调试调优阶段;
-
- 该线程中不存在已创建未销毁的子 Worker。