ArkTS 并发编程:TaskPool 多线程实战

ArkTS

ArkTS 并发编程:TaskPool 多线程实战

适用版本 :HarmonyOS 6.1(API 12)及以上

验证环境 :Pura 90 Pro 模拟器(HarmonyOS 6.1.1,API 24)

关键 API@kit.ArkTStaskpool


前言

移动应用中最常见的卡顿原因之一,就是在主线程里跑了耗时运算------图片解码、数据加密、大量数学计算......一旦主线程被占满,UI 渲染帧就会丢失,用户就会感受到明显卡顿。

HarmonyOS 提供了 TaskPool(任务池) 来解决这个问题:把计算密集型任务扔进线程池,主线程继续响应用户操作,任务完成后通过 await 自然地拿回结果更新 UI。本文用三个并发计算任务演示完整用法。


核心概念

TaskPool 工作原理

复制代码
主线程(UI)                子线程池(TaskPool)
   │                              │
   │── taskpool.execute(task) ──▶ │ 线程 1: computeFib(40)
   │── taskpool.execute(task) ──▶ │ 线程 2: computeFib(45)
   │── taskpool.execute(task) ──▶ │ 线程 3: computePrimes(500000)
   │                              │
   │◀── await 结果 ─────────────── │ 三个任务并发,各自独立
   │ 更新 UI(@State 驱动刷新)     │

三个关键规则:

  1. @Concurrent 装饰器 :任务函数必须是顶层函数 ,且加 @Concurrent 标注,编译器会验证其可序列化性
  2. 数据传递靠序列化:参数和返回值在跨线程传输时会被序列化,不能传递复杂对象(如 UI 组件)
  3. await 在主线程恢复taskpool.execute() 返回 Promise,await 之后的代码自动回到主线程,可安全更新 @State

完整代码

任务函数(顶层 + @Concurrent

typescript 复制代码
import { taskpool } from '@kit.ArkTS'

// ✅ 必须是顶层函数,不能是类方法或 lambda
@Concurrent
function computeFibonacci(n: number): number {
  if (n <= 1) return n
  let a = 0
  let b = 1
  for (let i = 2; i <= n; i++) {
    const c = a + b
    a = b
    b = c
  }
  return b
}

@Concurrent
function computePrimes(limit: number): number {
  // 线性筛,返回质数个数
  const sieve: boolean[] = new Array<boolean>(limit + 1).fill(true)
  sieve[0] = false
  if (limit >= 1) { sieve[1] = false }
  for (let i = 2; i * i <= limit; i++) {
    if (sieve[i]) {
      for (let j = i * i; j <= limit; j += i) {
        sieve[j] = false
      }
    }
  }
  let count = 0
  for (let k = 0; k <= limit; k++) {
    if (sieve[k]) { count++ }
  }
  return count
}

状态类型定义

typescript 复制代码
// ArkTS 严格模式下不支持 interface 字面量初始化,用 class
type TaskStatus = 'idle' | 'running' | 'done' | 'error'

class TaskResult {
  label: string = ''
  result: string = '-'
  duration: number = 0
  status: TaskStatus = 'idle'
}

页面组件(核心逻辑)

typescript 复制代码
@Entry
@Component
struct TaskPoolDemo {
  @State tasks: TaskResult[] = TaskPoolDemo.initTasks()
  @State runningCount: number = 0
  @State totalTime: number = 0

  static initTasks(): TaskResult[] {
    const t0 = new TaskResult()
    t0.label = 'Fibonacci(40)'
    const t1 = new TaskResult()
    t1.label = 'Fibonacci(45)'
    const t2 = new TaskResult()
    t2.label = 'Primes < 500000'
    return [t0, t1, t2]
  }

  // 不可变更新:每次生成新数组触发 @State 刷新
  private setTask(index: number, result: string, duration: number, status: TaskStatus): void {
    const updated: TaskResult[] = []
    for (let i = 0; i < this.tasks.length; i++) {
      const t = new TaskResult()
      t.label = this.tasks[i].label
      if (i === index) {
        t.result = result
        t.duration = duration
        t.status = status
      } else {
        t.result = this.tasks[i].result
        t.duration = this.tasks[i].duration
        t.status = this.tasks[i].status
      }
      updated.push(t)
    }
    this.tasks = updated
  }

  private async runFibTask(index: number, n: number): Promise<void> {
    this.setTask(index, '计算中...', 0, 'running')
    const start = Date.now()
    try {
      const task = new taskpool.Task(computeFibonacci, n)
      const result = await taskpool.execute(task) as number
      // await 之后自动在主线程,可直接更新 @State
      this.setTask(index, result.toString(), Date.now() - start, 'done')
    } catch (e) {
      const err = e as Error
      this.setTask(index, `错误: ${err.message}`, 0, 'error')
    }
  }

  private async runPrimeTask(index: number, limit: number): Promise<void> {
    this.setTask(index, '计算中...', 0, 'running')
    const start = Date.now()
    try {
      const task = new taskpool.Task(computePrimes, limit)
      const count = await taskpool.execute(task) as number
      this.setTask(index, `共 ${count} 个质数`, Date.now() - start, 'done')
    } catch (e) {
      const err = e as Error
      this.setTask(index, `错误: ${err.message}`, 0, 'error')
    }
  }

  private async runAllTasks(): Promise<void> {
    const globalStart = Date.now()
    this.runningCount = 3
    // Promise.all 让三个任务真正并发,而不是串行等待
    await Promise.all([
      this.runFibTask(0, 40),
      this.runFibTask(1, 45),
      this.runPrimeTask(2, 500000),
    ])
    this.totalTime = Date.now() - globalStart
    this.runningCount = 0
  }
  
  // ... build() 见完整源码
}

完整源码entry/src/main/ets/pages/Index.ets(见配套仓库)


模拟器运行效果

初始状态

三个任务均处于「等待」状态,绿色按钮可点击。


执行完成状态

点击「并发执行全部」后,三个任务在线程池中并发执行,主线程 UI 不阻塞。

实测结果:

任务 结果 耗时
Fibonacci(40) 102,334,155 46ms
Fibonacci(45) 1,134,903,170 44ms
Primes < 500000 41,538 个质数 69ms
总并发耗时 --- 76ms

三个任务各自独立,总耗时约等于最慢单任务(69ms),而非三者之和(159ms),验证了真正的并发执行。


关键点总结

1. @Concurrent 是强制要求

不加装饰器会在编译期报错。这是编译器的静态保证,确保函数内没有引用任何不可序列化的外部状态(如 UI 组件、闭包捕获变量)。

2. ArkTS 不支持 { ...spread } 更新对象

ArkTS 严格模式(arkts-no-spread)禁止对象扩展运算符。更新 @State 数组时需要手动遍历创建新数组,这也符合不可变数据原则。

3. Promise.all 真并发 vs 串行 await

typescript 复制代码
// ❌ 串行:总耗时 = 任务1 + 任务2 + 任务3
await task1(); await task2(); await task3();

// ✅ 并发:总耗时 ≈ max(任务1, 任务2, 任务3)
await Promise.all([task1(), task2(), task3()]);

4. 错误处理不能省略

TaskPool 任务失败会以 rejected Promise 的形式返回,必须用 try/catch 捕获,否则未处理的 rejection 会导致应用崩溃。


常见问题

Q: 为什么不能用类方法作为 TaskPool 任务函数?

A: 类方法依赖 this,而 this 可能引用不可序列化的 UI 状态。编译器强制要求顶层函数,从根源上规避这类问题。

Q: TaskPool 和 Worker 如何选择?

A: 优先用 TaskPool------系统自动管理线程数量和生命周期,适合一次性计算任务。Worker 适合需要长驻后台、持续双向通信的场景(如 WebSocket 处理、音频流处理)。

Q: 可以向 TaskPool 任务传递数组吗?

A: 可以,数组会被序列化传递。但如果数组很大(MB 级),建议改用 SharedArrayBuffer + @Sendable 对象实现零拷贝传递,详见第 3 篇:Sendable 对象模型


下一篇:ArkTS Worker 线程:独立执行环境详解

相关推荐
不羁的木木1 小时前
HarmonyOS APP实战-基于Image Kit的图像处理APP - 第8篇:图片编码与保存
图像处理·华为·harmonyos
春卷同学1 小时前
HarmonyOS掌上记账APP开发实践第2篇:从零搭建记账应用 — MoneyTrack 工程全景解析![
华为·harmonyos
春卷同学1 小时前
HarmonyOS掌上记账APP开发实践第1篇:HarmonyOS 多模块 HAR 架构最佳实践 — MoneyTrack 多模块拆分之道
华为·架构·harmonyos
youtootech2 小时前
HarmonyOS《柚兔学伴》项目实战10-半模态与弹窗交互
华为·交互·harmonyos
g_z_x_3 小时前
鸿蒙 ArkTS 实战:Quiz Arena 从状态建模到交互闭环完整解析
华为·交互·harmonyos
用户0934077735143 小时前
HarmonyOS WPS Open SDK 实践:最小打开闭环与 enableEdit 分支处理
typescript·harmonyos
绝世番茄3 小时前
鸿蒙原生 ArkTS 布局方式之 Stack 悬浮按钮布局(HarmonyOS NEXT / API 24)
华为·harmonyos·鸿蒙
2301_768103493 小时前
HarmonyOS趣味相机实战第5篇:CameraKit资源释放、PixelMap生命周期与预览黑屏排查
性能优化·harmonyos·arkts·camerakit·pixelmap
花开彼岸天~4 小时前
鸿蒙应用开发实战【01】— 号码助手项目概述与开发环境搭建
华为·harmonyos·鸿蒙系统