《文件查询》二、TaskPool使用指南

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

前言

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

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


效果

一、TaskPool 概述

1.1 什么是 TaskPool

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

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

1.2 TaskPool vs Worker

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

1.3 适用场景

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

二、环境准备

2.1 导入模块

typescript 复制代码
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(属性值也必须是可序列化类型)

基础示例:

typescript 复制代码
@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.HIGH 0 高优先级,优先调度
taskpool.Priority.MEDIUM 1 中优先级(默认值)
taskpool.Priority.LOW 2 低优先级,最后调度
typescript 复制代码
// 不同优先级的任务
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() 创建:

typescript 复制代码
@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

typescript 复制代码
@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 函数。此模式不可取消任务

typescript 复制代码
@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 对象创建并执行任务,可以设置优先级可以取消

typescript 复制代码
@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) --- 执行任务组

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

typescript 复制代码
@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() 检查取消状态。

typescript 复制代码
@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 函数内部检查当前任务是否已被取消。

typescript 复制代码
@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 可以获得编译期类型检查:

typescript 复制代码
@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+)

typescript 复制代码
@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 函数:

typescript 复制代码
@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 错误处理

typescript 复制代码
@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 在子线程中统计应用沙箱目录下的文件信息:

typescript 复制代码
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 等系统模块:

typescript 复制代码
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 页面导航实现指南 --- 详情页跳转功能的添加步骤

  • 📕 小说查询案例总体介绍指南 --- 案例总体效果与架构介绍

  • 📓 小说初始化实现指南 --- 种子数据设计与批量文件初始化流程
    参考文档:

  • 官方 API 文档 - @ohos.taskpool

  • TaskPool 文件查询实践指南