HarmonyOS @ohos.taskpool(启动任务池)完整使用指南

前言

在 HarmonyOS 应用开发中,当我们需要执行耗时操作(如文件读写、数据处理、图片解码等)时,如果在主线程中直接执行,会导致 UI 卡顿甚至 ANR(Application Not Responding)。TaskPool(任务池) 正是为解决这一问题而设计的多线程方案。

本文将系统讲解 @ohos.taskpool 的核心概念、API 用法和最佳实践,并通过一个完整的实战示例帮助你快速上手。


效果

一、TaskPool 概述

1.1 什么是 TaskPool

TaskPool(任务池)为应用程序提供一个多线程运行环境,具有以下特点:

  • 降低资源消耗:复用工作线程,避免频繁创建和销毁线程的开销
  • 自动负载均衡:根据任务数量动态扩缩容工作线程
  • 无需管理生命周期:开发者只需关注任务本身,无需关心线程的创建与回收
  • 支持任务取消:可以在任务执行前或执行中尝试取消

1.2 TaskPool vs Worker

对比维度TaskPoolWorker
线程管理系统自动管理开发者手动创建和销毁
适用场景独立的、短时间的耗时任务需要长期运行的后台线程
通信方式通过 Promise 返回结果通过 postMessage 通信
任务取消支持不支持
并发数量系统动态调度受限于创建的 Worker 数量

1.3 适用场景

  • 文件读写操作(如批量查询、文件复制)
  • 图片/视频解码
  • 数据排序、过滤等计算密集型操作
  • 关系型数据库查询
  • 网络数据解析

二、环境准备

2.1 导入模块

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

2.2 SDK 版本要求

  • 首批接口从 API version 9 开始支持
  • TaskGroupAPI version 10 开始支持
  • GenericsTask(泛型任务)从 API version 13 开始支持

三、核心概念详解

3.1 @Concurrent 装饰器

@Concurrent 是 TaskPool 的灵魂。所有在任务池中执行的函数必须使用 @Concurrent 装饰器标记。

基本规则:

  • 只能装饰顶层函数(不能是类方法或闭包)
  • 参数和返回值必须是可序列化类型
  • 函数内部不可访问 UI 上下文(如 getContext
  • 函数内部不可访问外部非 @Concurrent 模块的状态

可序列化的数据类型包括:

类型类别具体类型
基本类型number, string, boolean, null, undefined
日期Date
正则RegExp
集合Array, Map, Set
二进制ArrayBuffer, TypedArray
对象普通 Object(属性值也必须是可序列化类型)

基础示例:

@Concurrent
function calculateSum(start: number, end: number): number {
  let sum: number = 0;
  for (let i: number = start; i <= end; i++) {
    sum += i;
  }
  return sum;
}

常见错误:

错误码 10200014: The function is not marked as concurrent.

出现此错误说明传入 taskpool.execute 的函数没有使用 @Concurrent 标记。

3.2 Priority 优先级

TaskPool 提供三个优先级等级:

枚举值数值说明
taskpool.Priority.HIGH0高优先级,优先调度
taskpool.Priority.MEDIUM1中优先级(默认值)
taskpool.Priority.LOW2低优先级,最后调度
// 不同优先级的任务
taskpool.execute(task1, taskpool.Priority.HIGH);   // 高优先级
taskpool.execute(task2, taskpool.Priority.MEDIUM);  // 中优先级(默认)
taskpool.execute(task3, taskpool.Priority.LOW);     // 低优先级

3.3 Task 任务对象

Task 是 TaskPool 中任务的载体,通过 new taskpool.Task() 创建:

@Concurrent
function processData(input: string): string {
  return input.toUpperCase();
}

// 创建任务
let task: taskpool.Task = new taskpool.Task(processData, "hello world");

// 执行任务(可设置优先级,可取消)
taskpool.execute(task, taskpool.Priority.MEDIUM).then((result: Object) => {
  console.info("结果: " + result);  // 输出: HELLO WORLD
});

3.4 TaskGroup 任务组

当需要批量执行一组关联任务时,使用 TaskGroup

@Concurrent
function square(n: number): number {
  return n * n;
}

let group: taskpool.TaskGroup = new taskpool.TaskGroup();
group.addTask(square, 10);
group.addTask(square, 20);
group.addTask(square, 30);

taskpool.execute(group).then((results: Object[]) => {
  console.info("结果数组: " + results);  // 输出: 100,400,900
});

四、API 详解与示例

4.1 execute(func, …args) — 直接执行函数

最简单的用法,直接传入 @Concurrent 函数。此模式不可取消任务

@Concurrent
function fibonacci(n: number): number {
  if (n <= 1) return n;
  let a: number = 0, b: number = 1;
  for (let i: number = 2; i <= n; i++) {
    let temp: number = a + b;
    a = b;
    b = temp;
  }
  return b;
}

// 方式一:Promise 链式调用
taskpool.execute(fibonacci, 40).then((result: Object) => {
  console.info("Fibonacci(40) = " + result);
});

// 方式二:async/await
async function runFibonacci(): Promise<void> {
  let result: Object = await taskpool.execute(fibonacci, 40);
  console.info("Fibonacci(40) = " + result);
}

4.2 execute(task, priority) — 执行 Task 对象

通过 Task 对象创建并执行任务,可以设置优先级可以取消

@Concurrent
function sortArray(arr: Array<number>): Array<number> {
  for (let i: number = 0; i < arr.length - 1; i++) {
    for (let j: number = i + 1; j < arr.length; j++) {
      if (arr[j] < arr[i]) {
        let temp: number = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
      }
    }
  }
  return arr;
}

let data: Array<number> = [5, 3, 8, 1, 9, 2, 7, 4, 6, 0];
let task: taskpool.Task = new taskpool.Task(sortArray, data);

taskpool.execute(task, taskpool.Priority.HIGH).then((result: Object) => {
  let sorted: Array<number> = result as Array<number>;
  console.info("排序结果: " + sorted);
});

4.3 execute(group, priority) — 执行任务组

将多个任务打包为一组,全部完成后统一返回结果

@Concurrent
function fetchConfig(key: string): string {
  // 模拟配置读取
  let configs: Record<string, string> = {
    "theme": "dark",
    "language": "zh-CN",
    "fontSize": "16"
  };
  return configs[key] ?? "unknown";
}

let group: taskpool.TaskGroup = new taskpool.TaskGroup();
group.addTask(fetchConfig, "theme");
group.addTask(fetchConfig, "language");
group.addTask(fetchConfig, "fontSize");

taskpool.execute(group, taskpool.Priority.MEDIUM).then((results: Object[]) => {
  for (let i: number = 0; i < results.length; i++) {
    console.info("配置项 " + i + ": " + results[i]);
  }
});

4.4 cancel(task) — 取消任务

取消正在排队或正在执行的任务。需要在 @Concurrent 函数内部通过 taskpool.Task.isCanceled() 检查取消状态。

@Concurrent
function longRunningTask(total: number): number {
  let sum: number = 0;
  for (let i: number = 0; i < total; i++) {
    // 每个迭代检查是否被取消
    if (taskpool.Task.isCanceled()) {
      console.info("任务在第 " + i + " 次迭代时被取消");
      return sum;  // 提前退出
    }
    sum += i;
    // 模拟耗时操作
    let start: number = Date.now();
    while (Date.now() - start < 10) {
      // busy wait 10ms
    }
  }
  return sum;
}

let task: taskpool.Task = new taskpool.Task(longRunningTask, 10000);

taskpool.execute(task).then((result: Object) => {
  console.info("任务完成,结果: " + result);
});

// 1 秒后取消任务
setTimeout(() => {
  taskpool.cancel(task);
  console.info("已发送取消请求");
}, 1000);

关键要点:

  • cancel 只发送取消信号,不会强制终止线程
  • 必须在 @Concurrent 函数内部主动调用 taskpool.Task.isCanceled() 检查
  • 建议在循环、耗时操作的迭代中加入检查点

4.5 Task.isCanceled() — 取消状态检查

isCanceled() 是一个静态方法,用于在 @Concurrent 函数内部检查当前任务是否已被取消。

@Concurrent
function processWithCheckpoints(dataSize: number): string {
  // 检查点 1:任务开始
  if (taskpool.Task.isCanceled()) {
    return "任务在开始前被取消";
  }

  // 第一阶段处理
  let phase1: string = "第一阶段完成";

  // 检查点 2:第一阶段完成后
  if (taskpool.Task.isCanceled()) {
    return phase1 + ",任务在第二阶段前被取消";
  }

  // 第二阶段处理
  let start: number = Date.now();
  while (Date.now() - start < 2000) {
    if (taskpool.Task.isCanceled()) {
      return phase1 + ",任务在第二阶段中被取消";
    }
  }

  return "全部处理完成";
}

五、进阶用法

5.1 GenericsTask — 类型安全的任务执行(API 13+)

使用 GenericsTask 可以获得编译期类型检查:

@Concurrent
function multiply(a: number, b: number): number {
  return a * b;
}

// 使用 GenericsTask 获得类型安全
let task: taskpool.Task = new taskpool.GenericsTask<[number, number], number>(multiply, 6, 7);

taskpool.execute<[number, number], number>(task).then((result: number) => {
  console.info("6 × 7 = " + result);  // 类型安全,result 自动推断为 number
});

5.2 executeDelayed — 延时执行(API 11+)

@Concurrent
function delayedJob(): string {
  return "延时任务执行完成";
}

let task: taskpool.Task = new taskpool.Task(delayedJob);

// 延迟 2000ms 后执行
taskpool.executeDelayed(2000, task, taskpool.Priority.MEDIUM)
  .then((result: Object) => {
    console.info("结果: " + result);
  });

5.3 异步函数支持

@Concurrent 也可以装饰 async 函数:

@Concurrent
async function asyncTask(url: string): Promise<string> {
  // 在子线程中执行异步操作
  let result: string = await new Promise<string>((resolve) => {
    setTimeout(() => resolve("从 " + url + " 获取的数据"), 1000);
  });
  return result;
}

taskpool.execute(asyncTask, "https://api.example.com").then((result: Object) => {
  console.info(result);
});

5.4 错误处理

@Concurrent
function riskyTask(divisor: number): number {
  if (divisor === 0) {
    throw new Error("除数不能为零");
  }
  return 100 / divisor;
}

let task: taskpool.Task = new taskpool.Task(riskyTask, 0);

taskpool.execute(task)
  .then((result: Object) => {
    console.info("结果: " + result);
  })
  .catch((error: Error) => {
    console.error("任务执行出错: " + error.message);
  });

常见错误码:

错误码含义解决方案
401参数错误检查参数类型和数量
10200006序列化异常确保参数和返回值是可序列化类型
10200014函数未标记 @Concurrent添加 @Concurrent 装饰器
10200015取消时任务不存在确认任务已被提交到任务池
10200016取消时任务正在执行在函数内部使用 isCanceled() 检查
10200051周期任务重复执行长时任务仅支持执行一次

六、实战示例:文件统计工具

以下是一个完整的实战示例,演示如何使用 TaskPool 在子线程中统计应用沙箱目录下的文件信息:

import { taskpool } from '@kit.ArkTS';
import { fileIo } from '@kit.CoreFileKit';

// 定义可序列化的结果接口
interface FileStatResult {
  fileName: string;
  fileSize: number;
  isFile: boolean;
}

@Concurrent
function scanDirectory(dirPath: string): Array<FileStatResult> {
  let results: Array<FileStatResult> = [];
  let files: Array<string> = fileIo.listFileSync(dirPath);

  for (let i: number = 0; i < files.length; i++) {
    let filePath: string = dirPath + '/' + files[i];
    let stat: fileIo.Stat = fileIo.statSync(filePath);
    let item: FileStatResult = {
      fileName: files[i],
      fileSize: stat.size,
      isFile: stat.isFile()
    };
    results.push(item);
  }

  // 按文件大小降序排列
  for (let i: number = 0; i < results.length - 1; i++) {
    for (let j: number = i + 1; j < results.length; j++) {
      if (results[j].fileSize > results[i].fileSize) {
        let temp: FileStatResult = results[i];
        results[i] = results[j];
        results[j] = temp;
      }
    }
  }

  return results;
}

// 在页面中使用
async function startScan(filesDir: string): Promise<void> {
  let task: taskpool.Task = new taskpool.Task(scanDirectory, filesDir);
  let result: Object = await taskpool.execute(task, taskpool.Priority.HIGH);
  let fileList: Array<FileStatResult> = result as Array<FileStatResult>;

  for (let i: number = 0; i < fileList.length; i++) {
    let item: FileStatResult = fileList[i];
    console.info(`${item.fileName}: ${item.fileSize} bytes`);
  }
}

七、最佳实践与常见陷阱

7.1 最佳实践

  1. 耗时操作放子线程:超过 16ms 的操作建议使用 TaskPool
  2. 合理使用优先级:用户交互相关用 HIGH,后台任务用 LOW
  3. 大数据使用 ArrayBuffer:传输大量二进制数据时,使用 setTransferList 避免复制开销
  4. 循环中检查取消状态:长时间运行的任务应在循环迭代中检查 isCanceled()
  5. 使用 for 循环替代高阶函数:ArkTS 的 Array.frommapfilter 等方法存在泛型推断限制,建议使用 for 循环
  6. 避免在子线程中访问 rawfile:rawfile 需要通过 resourceManager API 访问,子线程不可用;将数据嵌入代码作为参数传入子线程

7.2 常见陷阱

陷阱说明正确做法
在 @Concurrent 中访问 UI子线程无法获取 getContext将数据通过参数传入,结果通过 Promise 返回
在 @Concurrent 中访问 rawfile子线程无法通过 fileIo 访问 rawfile 路径,抛出 No such file or directory将数据嵌入代码作为参数传入,或在主线程通过 resourceManager 读取后传入
传入类实例类实例不可序列化使用普通 interface 作为传输载体
忘记标记 @Concurrent运行时抛出 10200014 错误所有在 TaskPool 中执行的函数必须标记
在任务中做无限期阻塞占据工作线程,影响其他任务设置超时或使用 isCanceled() 检查点
大量创建任务内存压力过大使用 TaskGroup 批量管理,控制并发量

7.3 @Concurrent 函数中的 import

@Concurrent 函数可以使用通过 import 导入的模块,例如 fileIoutil 等系统模块:

import { fileIo } from '@kit.CoreFileKit';
import { util } from '@kit.ArkTS';

@Concurrent
function readFileContent(filePath: string): string {
  let fd: fileIo.File = fileIo.openSync(filePath, fileIo.OpenMode.READ_ONLY);
  let stat: fileIo.Stat = fileIo.statSync(fd.fd);
  let buf: ArrayBuffer = new ArrayBuffer(stat.size);
  fileIo.readSync(fd.fd, buf);
  fileIo.closeSync(fd.fd);
  let decoder: util.TextDecoder = util.TextDecoder.create('utf-8');
  return decoder.decodeWithStream(new Uint8Array(buf));
}

八、总结

@ohos.taskpool 是 HarmonyOS 中实现多线程的核心工具,其优势在于:

  • 简单易用:只需 @Concurrent + execute 即可将任务放到子线程
  • 自动管理:线程池自动扩缩容,无需手动管理生命周期
  • 类型安全GenericsTask 提供编译期类型检查
  • 灵活调度:支持优先级、延时执行、任务取消等高级特性

在实际开发中,建议遵循以下原则:

  1. 耗时操作交给 TaskPool,保持主线程流畅
  2. 使用普通 interface 作为跨线程数据传输载体
  3. 在长任务中设置取消检查点
  4. 合理选择任务优先级,优化用户体验

系列文档:

  • 📘 本文 — TaskPool API 详解与使用规范
  • 📗 小说文件查询案例指南 — 基础查询功能的完整实现过程
  • 📙 Navigation 页面导航实现指南 — 详情页跳转功能的添加步骤
  • 📕 小说查询案例总体介绍指南 — 案例总体效果与架构介绍
  • 📓 小说初始化实现指南 — 种子数据设计与批量文件初始化流程

参考文档:

Logo

免费领 150 小时云算力,进群参与显卡、AI PC 幸运抽奖

更多推荐