在这里插入图片描述
在这里插入图片描述

一、引言

在现代应用开发中,性能是用户体验的核心。当应用需要处理计算密集型任务(如大数据计算、图像处理、加密解密)时,如果都在主线程执行,会导致界面卡顿、无响应。HarmonyOS 提供了强大的并发编程能力,其中 TaskPool(任务池) 是最常用的并发执行方案。

本文将以一个青绿网格风格的并发编程演示页面为主线,深入讲解 TaskPool 的使用方法、底层原理和最佳实践,帮助读者掌握并发编程的核心技能。

二、为什么需要并发

2.1 主线程的职责

HarmonyOS 应用中,主线程(UI 线程)负责处理 UI 渲染和事件响应。主线程的职责包括:

  1. 处理用户交互事件(点击、滑动、输入)。
  2. 执行 UI 渲染。
  3. 处理系统消息。

如果主线程被耗时任务阻塞,用户会感觉到界面卡顿、无响应,甚至触发系统"应用无响应"(ANR)提示。

2.2 耗时任务的危害

以下任务如果放在主线程执行,会造成严重卡顿:

  • 大量数据计算(如 1000 万次循环累加)。
  • 图片解码和缩放。
  • JSON 大数据解析。
  • 文件读写。
  • 网络请求(应使用异步 API)。

2.3 并发方案对比

HarmonyOS 提供两种主要的并发方案:

方案 特点 适用场景
TaskPool 线程池复用,自动调度,任务级并发 计算密集型任务
Worker 独立线程,长生命周期,消息通信 长耗时任务、常驻任务

三、TaskPool 核心概念

3.1 什么是 TaskPool

TaskPool(任务池)是 HarmonyOS 提供的一种任务级并发执行框架。它的核心思想是:

  1. 维护一个线程池,复用线程资源。
  2. 开发者提交任务(函数),框架自动调度到空闲线程执行。
  3. 任务执行完成后,结果返回给调用方。

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 是一个计算密集型任务函数:

  1. @Concurrent 装饰:标记该函数可以提交到 TaskPool 执行。

  2. 函数逻辑:计算从 1 到 n 的累加和。当 n 很大(如 1000 万)时,这是一个耗时操作,适合放到后台线程执行。

  3. 参数和返回值:参数 n 是 number,返回值是 number,都是可序列化的基本类型,满足 TaskPool 的要求。

  4. 为什么用 @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 使用的核心:

  1. 防重入if (this.running) return 防止重复提交任务。

  2. 更新状态:将任务状态更新为"运行中",由于 tasks@State 数组,UI 会自动刷新。

  3. 提交任务taskpool.execute(computeSum, [10000000]) 是核心调用:

    • 第一个参数:要执行的函数(必须是 @Concurrent 装饰的)。
    • 第二个参数:函数参数数组,[10000000] 表示传入 n = 10000000
    • 返回 Promise,await 等待任务完成。
  4. 获取结果:任务完成后,返回值 result 就是 computeSum 的计算结果(1 到 1000 万的和)。

  5. 计算耗时:用 Date.now() 记录开始和结束时间,计算任务耗时。

  6. 更新完成状态:将任务状态更新为"完成",并显示计算结果和耗时。

  7. 异常处理catch 块处理任务执行异常,将状态更新为"失败"。

  8. 恢复标志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 网格布局:

  1. Grid 布局.columnsTemplate('1fr 1fr') 设置两列等宽,.columnsGap(10).rowsGap(10) 设置行列间距。

  2. 任务卡片:每个 GridItem 包含任务名称、状态、耗时三个信息。

  3. 状态颜色:空闲状态使用灰色文字,运行中/完成状态使用白色文字,通过条件判断实现。

  4. 点击执行:点击卡片调用 runTask(index) 执行对应任务。

  5. 网格风格:直角边框、青绿配色,与页面整体风格统一。

五、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 枚举包含 LOWMEDIUMHIGH 三个优先级。
  • 高优先级任务会被优先调度执行。

七、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 并发编程,通过一个青绿网格风格的演示页面实战演示了任务提交、并行执行、结果获取等核心能力。

核心要点回顾:

  1. 主线程负责 UI,耗时任务必须放到后台线程。
  2. @Concurrent 标记可并行执行的函数。
  3. taskpool.execute() 提交任务,返回 Promise。
  4. TaskGroup 批量执行任务并统一等待。
  5. Task 对象支持设置优先级。
  6. TaskPool 适合一次性计算任务,Worker 适合常驻任务。
  7. 避免共享可变状态,正确处理异步。

并发编程是提升应用性能的关键技术,掌握它能让应用在多核设备上发挥最大性能。下一篇我们将讲解 HarmonyOS 路由导航。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐