适合读者:有一定移动端开发经验(Android / iOS / 前端),想深入理解 HarmonyOS NEXT 并发机制的开发者。


一、从一个痛点出发

做过 Android 开发的同学一定遇过这个经典噩梦:

// 多线程竞争同一个 List,轻轻松松触发 ConcurrentModificationException
new Thread(() -> list.add(item)).start();
new Thread(() -> list.remove(0)).start();

锁(Lock)、同步块(synchronized)、volatile……为了保护共享内存,我们堆了一层又一层的防护。代码越写越脆,Bug 越来越难复现。

HarmonyOS NEXT 在设计 ArkTS 并发体系时,选择了一条不同的路:不让你碰共享内存

这背后的理论支撑叫做 Actor 并发模型,而它的工程落地,就是 ArkTS 里的 TaskPoolWorker


二、两种并发世界观

先来理解两种主流并发模型的根本区别。

2.1 内存共享模型(Shared Memory Concurrency)

这是 Java/C++ 的传统方式:多个线程共享同一块堆内存,通过"抢锁"来决定谁有权访问。

Thread A ─────────────────┐
                           ├─── 竞争访问 ──► 共享内存 (Heap)
Thread B ─────────────────┘
          ↑ 谁先拿到锁,谁先操作

问题:死锁、竞态条件(Race Condition)、内存可见性问题……这些都是程序员的梦魇。即便是经验丰富的工程师也经常在这里翻车。

2.2 Actor 模型(Message-Passing Concurrency)

Actor 模型的核心理念只有一句话:每个线程是一个独立的 Actor,有自己私有的内存;Actor 之间只能通过消息通信,不能直接访问彼此的内存。

Actor A (主线程)                    Actor B (子线程)
┌─────────────────┐                ┌─────────────────┐
│  私有 VM 实例    │  ──消息/序列化──►  │  私有 VM 实例    │
│  私有堆内存      │  ◄──消息/序列化──  │  私有堆内存      │
└─────────────────┘                └─────────────────┘
        ↑ 互不干扰,零竞争

没有共享,就没有竞争。没有竞争,就不需要锁。这是 ArkTS 并发设计的第一性原理。


三、ArkTS 并发的两张牌:TaskPool vs Worker

HarmonyOS NEXT 基于 Actor 模型,提供了两个多线程并发 API:TaskPool(任务池)和 Worker(工作线程)。它们是同一套模型的不同封装层次。

3.1 快速对比

维度 TaskPool Worker
抽象层次 任务(Task)维度 线程维度
生命周期 系统自动管理 开发者手动管理
适用任务时长 短时任务(< 3分钟)¹ 长时/常驻任务
线程数量 系统自动扩缩容 最多 64 个
优先级调度 ✅ 支持 ❌ 不支持
任务取消 ✅ 支持 ❌ 不支持
推荐场景 绝大多数场景 需要持久化线程句柄的场景

一句话总结:优先用 TaskPool,只有当你需要管理线程生命周期或任务超长时,才用 Worker

¹ 超过 3 分钟的任务会被系统自动回收,但可以用 new taskpool.LongTask(fn, ...args) 替代 taskpool.Task 来声明长时任务,突破这个限制,同时仍保留 TaskPool 的调度优势。


四、TaskPool 实战拆解

4.1 最简用法

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

// ⚠️ 关键点:必须用 @Concurrent 装饰器标记,且只能用导入变量和局部变量
@Concurrent
function heavyCompute(n: number): number {
  let result = 0;
  for (let i = 0; i < n; i++) {
    result += Math.sqrt(i);
  }
  return result;
}

@Entry
@Component
struct Index {
  @State result: string = '未计算';

  async runTask() {
    const task = new taskpool.Task(heavyCompute, 10_000_000);
    // execute 返回 Promise,await 即可拿到结果
    const res = await taskpool.execute(task) as number;
    this.result = `计算结果: ${res.toFixed(2)}`;
  }

  build() {
    Column({ space: 20 }) {
      Text(this.result).fontSize(18)
      Button('开始计算').onClick(() => this.runTask())
    }
    .width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}

主线程完全不阻塞,UI 依旧丝滑。这就是 TaskPool 的价值。

4.2 任务优先级与任务组

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

@Concurrent
function processImage(imageId: string): string {
  // 模拟图片处理耗时
  return `processed_${imageId}`;
}

async function batchProcess(imageIds: string[]) {
  const taskGroup = new taskpool.TaskGroup();

  for (const id of imageIds) {
    const task = new taskpool.Task(processImage, id);
    taskGroup.addTask(task);
  }

  // 等待所有任务完成,类似 Promise.all
  const results = await taskpool.execute(taskGroup) as string[];
  console.log('全部处理完成:', results);
}

// 高优先级的紧急任务
async function urgentTask(id: string) {
  const task = new taskpool.Task(processImage, id);
  // 设置为最高优先级,系统会优先调度
  await taskpool.execute(task, taskpool.Priority.HIGH);
}

4.3 可取消任务(图库场景)

这是 TaskPool 相比 Worker 独有的能力,非常适合图片预加载这类"滑动时需要频繁取消旧任务"的场景:

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

@Concurrent
function loadImage(url: string): ArrayBuffer {
  // 模拟网络请求
  return new ArrayBuffer(1024);
}

class ImagePreloader {
  private taskMap: Map<string, taskpool.Task> = new Map();

  preload(url: string) {
    const task = new taskpool.Task(loadImage, url);
    this.taskMap.set(url, task);
    taskpool.execute(task).then(data => {
      console.log(`${url} 加载完成`);
    }).catch(err => {
      // 被取消时会走到这里
      console.log(`${url} 任务已取消`);
    });
  }

  cancel(url: string) {
    const task = this.taskMap.get(url);
    if (task) {
      // ⚠️ 重要限制:cancel 只对还在队列中等待的任务有效
      // 如果任务已被线程取走、正在执行,cancel 调用是无效的
      taskpool.cancel(task);
      this.taskMap.delete(url);
    }
  }
}

五、Worker 实战拆解

当你需要一个"长期存活"的线程(比如维持一个 WebSocket 连接,或需要持久化的数据库句柄),就要用 Worker

5.1 Worker 线程文件(workers/MyWorker.ets)

import { workerPort, MessageEvents } from '@ohos.worker';

// Worker 线程的入口,监听主线程消息
workerPort.onmessage = (event: MessageEvents) => {
  const { type, payload } = event.data as { type: string; payload: unknown };

  if (type === 'COMPUTE') {
    const n = payload as number;
    let result = 0;
    for (let i = 0; i < n; i++) result += i;
    // 将结果发回主线程(自动序列化)
    workerPort.postMessage({ type: 'RESULT', payload: result });
  }
};

workerPort.onmessageerror = (event: MessageEvents) => {
  console.error('Worker 消息错误', event);
};

5.2 主线程使用 Worker

import worker from '@ohos.worker';

@Entry
@Component
struct WorkerDemo {
  private myWorker: worker.ThreadWorker | null = null;
  @State output: string = '等待结果...';

  aboutToAppear() {
    // 创建 Worker,路径为 Worker 文件的相对路径
    this.myWorker = new worker.ThreadWorker('entry/ets/workers/MyWorker');

    // 监听 Worker 返回的消息
    this.myWorker.onmessage = (event: worker.MessageEvents) => {
      const { payload } = event.data as { type: string; payload: number };
      this.output = `Worker 计算结果: ${payload}`;
    };
  }

  aboutToDisappear() {
    // ⚠️ 必须手动销毁,否则线程泄漏!
    this.myWorker?.terminate();
  }

  build() {
    Column({ space: 20 }) {
      Text(this.output).fontSize(18)
      Button('发送任务给 Worker').onClick(() => {
        this.myWorker?.postMessage({ type: 'COMPUTE', payload: 1_000_000 });
      })
    }
    .width('100%').height('100%').justifyContent(FlexAlign.Center)
  }
}

六、最难的部分:跨线程数据怎么传?

这是很多开发者遇坑最多的地方。由于 Actor 模型内存隔离,数据跨线程传递需要"搬家"而非"共享地址"。ArkTS 支持四种方式,各有适用场景。

方式一:普通对象——序列化拷贝(最常用)

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

@Concurrent
function processData(data: Record<string, string>): string {
  // data 是从主线程深拷贝过来的,修改它不影响主线程
  return data.name.toUpperCase();
}

const obj = { name: 'harmony', version: '5.0' };
taskpool.execute(new taskpool.Task(processData, obj));
// 注意:obj 传入后是拷贝,子线程修改 obj 对主线程无影响

优点:安全简单。缺点:大对象拷贝有性能开销。

方式二:ArrayBuffer——可转移(Transfer)

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

@Concurrent
function encryptBuffer(buf: ArrayBuffer): ArrayBuffer {
  // 处理二进制数据
  const view = new Uint8Array(buf);
  for (let i = 0; i < view.length; i++) {
    view[i] ^= 0xFF; // 简单异或加密
  }
  return buf;
}

async function runEncrypt() {
  const buffer = new ArrayBuffer(1024 * 1024); // 1MB
  const task = new taskpool.Task(encryptBuffer, buffer);

  // transfer 模式:所有权转移给子线程,主线程原始 buffer 变为空
  // 避免了 1MB 数据的内存拷贝!
  task.setTransferList([buffer]);
  const result = await taskpool.execute(task) as ArrayBuffer;
  console.log('加密完成,结果长度:', result.byteLength);
}

适用:大文件、图片、音视频等二进制场景。所有权转移,零拷贝。

方式三:SharedArrayBuffer——共享内存

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

// 罕见但有用:多线程同时写入同一块内存
const sharedBuf = new SharedArrayBuffer(4);

@Concurrent
function increment(buf: SharedArrayBuffer) {
  const arr = new Int32Array(buf);
  // 必须用 Atomics 保证操作原子性,防止数据竞争
  Atomics.add(arr, 0, 1);
}

async function runIncrement() {
  // 多个 TaskPool 任务共享同一块内存
  const tasks: Promise<Object>[] = [];
  for (let i = 0; i < 10; i++) {
    tasks.push(taskpool.execute(new taskpool.Task(increment, sharedBuf)));
  }
  await Promise.all(tasks);
  console.log('最终计数:', new Int32Array(sharedBuf)[0]); // 应输出 10
}

警告:这是 Actor 模型的"后门",使用时必须配合 Atomics,否则数据竞争问题重现。非必要不使用

方式四:Sendable 对象——引用共享(ArkTS 独有创新)

这是 HarmonyOS NEXT 相比传统 JS 引擎最大的创新之一。

传统 JS 引擎中,跨线程只能拷贝或转移,没有办法让多个线程引用同一个 JS 对象。但 Sendable 打破了这个限制:

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

// 用 @Sendable 装饰的类,其实例可以在线程间共享引用
@Sendable
class SharedConfig implements lang.ISendable {
  public readonly apiUrl: string;
  public readonly timeout: number;

  constructor(url: string, timeout: number) {
    this.apiUrl = url;
    this.timeout = timeout;
  }
}

@Concurrent
function fetchData(config: SharedConfig): string {
  // config 是跨线程的引用,不是拷贝
  // ⚠️ Sendable 对象的属性必须是只读或 Sendable 类型,防止并发写入
  return `请求 ${config.apiUrl},超时 ${config.timeout}ms`;
}

// 多个任务共享同一个配置对象,无需序列化拷贝
async function runFetch() {
  const config = new SharedConfig('https://api.example.com', 5000);
  const tasks: Promise<Object>[] = [];
  for (let i = 0; i < 5; i++) {
    tasks.push(taskpool.execute(new taskpool.Task(fetchData, config)));
  }
  const results = await Promise.all(tasks) as string[];
  console.log(results);
}

Sendable 的意义在于:对于那些大型只读配置、模型数据,不再需要每次都深拷贝一份给子线程,内存效率大幅提升。


七、完整选型决策树

需要多线程并发?
    │
    ├─ 任务是否需要长期占用线程(> 3分钟)或维持线程状态(数据库连接等)?
    │      ├─ 是 ──► 使用 Worker
    │      └─ 否 ──► 使用 TaskPool(推荐)
    │
传递的数据是什么类型?
    │
    ├─ 普通 JS 对象,数据量小 ──► 默认序列化拷贝
    ├─ ArrayBuffer / 大二进制数据 ──► Transfer 转移所有权
    ├─ 多线程都需要写入的共享计数器 ──► SharedArrayBuffer + Atomics
    └─ 大型只读对象(配置、模型)──► @Sendable 引用传递

八、几个常见的坑

坑 1:@Concurrent 函数里用了外部变量

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

const multiplier = 3; // 外部闭包变量

// ❌ 编译报错!ArkTS 编译器会拒绝这种写法
@Concurrent
function badFunc(n: number): number {
  return n * multiplier; // 不能访问外部变量
}

// ✅ 正确:通过参数传入
@Concurrent
function goodFunc(n: number, m: number): number {
  return n * m;
}
taskpool.execute(new taskpool.Task(goodFunc, 5, multiplier));

原因:@Concurrent 函数会被序列化发到子线程执行,子线程没有主线程的闭包上下文,编译期强制检查。

坑 2:Worker 忘记 terminate()

Worker 线程不会自动销毁。页面销毁(aboutToDisappear)时,必须手动调用实例的 terminate() 方法(即 myWorker.terminate()),否则会造成线程泄漏,严重时导致应用崩溃。

坑 3:把大对象扔进 TaskPool 期待高性能

如果你有一个 10MB 的 JSON 对象要传给子线程,序列化拷贝的开销可能比在主线程直接计算还慢。这时候应该考虑 Transfer(如果是 ArrayBuffer)或 @Sendable(如果是只读配置)。


九、横向对比:这套设计有多独特?

Android(Java/Kotlin) Web(JS Worker) ArkTS
默认并发模型 内存共享 + 锁 Actor(拷贝) Actor(拷贝)
跨线程对象共享 直接共享(有风险) ❌ 不支持 ✅ Sendable 引用传递
系统托管线程池 ExecutorService ✅ TaskPool
任务取消 Future.cancel() ✅ taskpool.cancel()
优先级调度 Thread.setPriority() ✅ taskpool.Priority

可以看到,ArkTS 在继承 JS/TS 生态的同时,在并发能力上做了相当大的增强——尤其是 Sendable 对象和 TaskPool 的系统级调度,是传统 Web Worker 所不具备的。


十、总结

ArkTS 的 Actor 并发模型,本质上是用"强制内存隔离"换来了"开发者不需要考虑锁"。这是一笔划算的买卖——大部分业务场景里,你并不需要跨线程共享可变状态;而那些真正需要共享的场景,SendableSharedArrayBuffer 提供了足够精细的控制。

理解这套模型之后,鸿蒙 NEXT 开发的几个原则自然就清晰了:

  1. 主线程只做 UI,所有耗时操作丢给 TaskPool
  2. 优先用 TaskPool,需要持久线程才用 Worker
  3. 数据传递按体量选择:小对象拷贝、大二进制转移、只读大对象 Sendable
  4. @Concurrent 函数不能有闭包,这是编译器替你保驾护航

鸿蒙生态正在快速演进,但这套并发设计的基础架构已经相当稳固。搞清楚它,是写出高性能鸿蒙原生应用的第一步。


Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐