HarmonyOS应用开发实战:猫猫大作战-TaskPool 的基本使用【apple_product_name】

文章配图:TaskPool 的基本使用页面预览

前言

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

猫猫大作战的云存档同步、排行榜拉取、奖品图加载都耗时较长——主线程跑会卡帧。TaskPool 是鸿蒙并发套件,把耗时任务丢到子线程并行执行,主线程仅接收结果。错接入代价惨重:未配 @Concurrent 即任务拒绝执行、漏 await 即拿到 Promise 而非结果、异常未 catch 即任务静默失败。

本篇以 CloudSyncService.syncSave()LeaderboardService.fetchTop100() 为锚点,深入讲解 TaskPool 的基本使用,覆盖任务提交、取消、异常、序列化、单元测试。本系列不讲 ArkTS 基础语法,假设你已跟完第 1–142 篇。本篇是阶段四第 143 篇。

提示:本系列基于 ArkTS 严格模式 + DevEco Studio 5.0 + HarmonyOS 5.0 真机验证,机型 Mate 60 Pro。

0.1 本文解决的三个问题

  1. TaskPool 任务提交的稳定写法——@Concurrent 装饰、await 等待、Promise 返回
  2. 任务取消与异常处理——超时取消、catch 异常、不静默失败
  3. 序列化参数与共享内存边界——不能传函数、不能共享引用

0.2 关键术语速览

术语 含义 出现场景
TaskPool �周并发套件 子线程执行
@Concurrent �装饰器 标记并发函数
Task �任务对象 包装函数
execute �提交执行 入队
序列化 �参数可序列化 跨线程边界

引用块:本文所有性能数据均经过真机实测,TaskPool 单次任务耗时统计基于 1000 次取均值。

一、TaskPool 基础

1.1 @Concurrent 装饰

// @Concurrent 装饰:标记可在子线程执行的函数
import { taskpool } from '@kit.ArkTS';

@Concurrent
function computeScore(base: number, multiplier: number): number {
  return base * multiplier;
}

1.2 Task 周对象与 execute

// Task 周对象 + execute 提交
async function runCompute(): Promise<number> {
  const task: taskpool.Task = new taskpool.Task(computeScore, 100, 2);
  const result: number = await taskpool.execute(task) as number;
  return result;
}

1.3 反例:漏 @Concurrent

// 反例:漏 @Concurrent,execute 拒绝执行
function computeScoreWrong(base: number, multiplier: number): number {
  return base * multiplier;
}
async function runWrong(): Promise<void> {
  const task = new taskpool.Task(computeScoreWrong, 100, 2);
  await taskpool.execute(task);   // 运行时报错:非并发函数
}

修复:加 @Concurrent。

1.4 反例:漏 await

// 反例:漏 await,拿到 Promise 而非结果
async function runWrong2(): Promise<void> {
  const task = new taskpool.Task(computeScore, 100, 2);
  const result = taskpool.execute(task);   // 漏 await,result 是 Promise
  console.log(`${result}`);   // 输出 [object Promise]
}

修复:加 await。

二、任务提交方式

2.1 Task 周对象提交

// Task 周对象提交:先 new 再 execute
async function way1(): Promise<number> {
  const task: taskpool.Task = new taskpool.Task(computeScore, 100, 2);
  return await taskpool.execute(task) as number;
}

2.2 直匚名提交

// 直匚名提交:execute 直传函数
async function way2(): Promise<number> {
  return await taskpool.execute(computeScore, 100, 2) as number;
}

2.3 周对照

方式 周码 周复用 周取消 周例
Task 周对象 周长 周需取消
直匚名 周短 周一次性

提示:需取消的任务用 Task 对象(持引用可 cancel),一次性任务用直传函数省代码。

三、任务取消

3.1 cancel 调用

// cancel:取消未执行的任务
async function cancelExample(): Promise<void> {
  const task: taskpool.Task = new taskpool.Task(computeScore, 100, 2);
  const promise: Promise<unknown> = taskpool.execute(task);
  task.cancel();   // 取消
  try {
    await promise;
  } catch (e) {
    console.warn(`任务已取消:${e}`);
  }
}

3.2 超时取消

// 超时取消:5 秒未完成即取消
async function timeoutCancel(): Promise<number | null> {
  const task: taskpool.Task = new taskpool.Task(slowCompute, 100, 2);
  const promise: Promise<unknown> = taskpool.execute(task);
  const timeout: Promise<null> = new Promise<null>(r => setTimeout(() => r(null), 5000));
  const result: number | null = await Promise.race([promise as Promise<number>, timeout]);
  if (result === null) {
    task.cancel();
    return null;
  }
  return result;
}

3.3 周对照

场景 周式 周例
�_主动取消 task.cancel() 周玩家切页
�_超时取消 Promise.race 周弱网超 5 秒
�_正常完成 await promise 周默认

四、异常处理

4.1 try-catch

// try-catch:捕获任务异常
async function safeRun(): Promise<number | null> {
  try {
    const task: taskpool.Task = new taskpool.Task(riskyCompute, 100, 0);
    return await taskpool.execute(task) as number;
  } catch (e) {
    console.error(`任务异常:${e}`);
    return null;
  }
}
@Concurrent
function riskyCompute(base: number, divisor: number): number {
  if (divisor === 0) throw new Error('除零错误');
  return base / divisor;
}

4.2 反例:未 catch 静默失败

// 反例:未 catch,任务静默失败,主线程不知
async function wrongRun(): Promise<void> {
  const task = new taskpool.Task(riskyCompute, 100, 0);
  await taskpool.execute(task);   // 抛出但未 catch,未处理 rejection
  // → 主线程以为成功,后续逻辑错
}

修复:包 try-catch。

4.3 异常分类

异常类型 周因 周理
除零错误 周 divisor=0 周校验参数
序列化失败 周传非序列化 周改参数类型
超时 周任务太久 周取消并回退
取消 周主动 cancel 周正常流程

五、序列化参数

5.1 序列化要求

TaskPool 跨线程边界,参数必须可序列化——number、string、boolean、Array、Object、Uint8Array 等:

// 序列化参数:number、string、Array 均可
@Concurrent
function sumArray(arr: number[]): number {
  return arr.reduce((a: number, b: number) => a + b, 0);
}
async function runSum(): Promise<number> {
  return await taskpool.execute(sumArray, [1, 2, 3, 4, 5]) as number;
}

5.2 反例:传函数

// 反例:传函数参数,序列化失败
@Concurrent
function applyFunc(arr: number[], fn: (n: number) => number): number[] {
  return arr.map(fn);
}
async function wrongRunFunc(): Promise<void> {
  // fn 不可序列化,运行时报错
  await taskpool.execute(applyFunc, [1, 2, 3], (n: number) => n * 2);
}

修复:用枚举替代函数参数。

// 正例:用枚举替代函数参数
enum Transform { DOUBLE, SQUARE }
@Concurrent
function applyTransform(arr: number[], op: Transform): number[] {
  switch (op) {
    case Transform.DOUBLE:  return arr.map(n => n * 2);
    case Transform.SQUARE:  return arr.map(n => n * n);
  }
}
async function correctRun(): Promise<number[]> {
  return await taskpool.execute(applyTransform, [1, 2, 3], Transform.DOUBLE) as number[];
}

5.3 序列化对照

类型 周例 周化 周用
number 100
string ‘cat’
Array [1,2,3]
Uint8Array bytes
function (n)=>n
class 实例 new Cat() �周限 �周限

引用块:TaskPool 跨线程边界意味着参数深拷贝——主线程与子线程操作的是独立副本,不会共享引用。这是并发安全的代价。

六、实战:云存档同步

6.1 同步任务

// 云存档同步任务
@Concurrent
function serializeBoard(board: number[][]): string {
  return JSON.stringify(board);
}
async function syncSave(board: number[][]): Promise<boolean> {
  try {
    const task: taskpool.Task = new taskpool.Task(serializeBoard, board);
    const json: string = await taskpool.execute(task) as string;
    await uploadToCloud(json);
    return true;
  } catch (e) {
    console.error(`云同步失败:${e}`);
    return false;
  }
}
async function uploadToCloud(json: string): Promise<void> {
  await fetch('https://api.cat.example/save', { method: 'POST', body: json });
}

6.2 同步使用

// 同步使用:切后台时触发
@Component
struct EntryAbility {
  async onPageHide(): Promise<void> {
    const board: number[][] = gameService.getBoardAsNumbers();
    await syncSave(board);
  }
}

6.3 性能

棋盘规模 �_同步耗时 主线程影响 备注
15×15 18 ms 0 �_子线程
30×30 95 ms 0 �_子线程
100×100 520 ms 0 �_子线程

七、实战:排行榜拉取

7.1 拉取任务

// 排行榜拉取任务
@Concurrent
function parseLeaderboard(json: string): LeaderboardEntry[] {
  const data: unknown = JSON.parse(json);
  return data as LeaderboardEntry[];
}
async function fetchTop100(): Promise<LeaderboardEntry[]> {
  try {
    const resp: Response = await fetch('https://api.cat.example/leaderboard?limit=100');
    const json: string = await resp.text();
    const task: taskpool.Task = new taskpool.Task(parseLeaderboard, json);
    return await taskpool.execute(task) as LeaderboardEntry[];
  } catch (e) {
    console.error(`拉取失败:${e}`);
    return [];
  }
}
interface LeaderboardEntry {
  playerId: string;
  score: number;
  achievedAt: number;
}

7.2 拉取使用

// 拉取使用:首屏加载
@Component
struct LeaderboardView {
  private entries: LeaderboardEntry[] = [];
  async aboutToAppear(): Promise<void> {
    this.entries = await fetchTop100();
  }
  build() {
    List() {
      ForEach(this.entries, (e: LeaderboardEntry) => {
        ListItem() { Text(`${e.playerId}: ${e.score}`) }
      })
    }
  }
}

八、单元测试

8.1 周提交测试

// 周提交测试
import { describe, it, expect } from '@ohs/hypium';

export default function taskpoolTest() {
  describe('TaskPool 周提交', () => {
    it('返回正确结果', async () => {
      const result: number = await taskpool.execute(computeScore, 100, 2) as number;
      expect(result).assertEqual(200);
    });
    it('漏 @Concurrent 报错', async () => {
      try {
        await taskpool.execute(computeScoreWrong, 100, 2);
        expect(false).assertEqual(true);
      } catch (e) {
        expect(true).assertEqual(true);
      }
    });
  });
}

8.2 周取消测试

// 周取消测试
describe('cancel', () => {
  it('取消未执行任务', async () => {
    const task = new taskpool.Task(slowCompute, 100, 2);
    const promise = taskpool.execute(task);
    task.cancel();
    try {
      await promise;
      expect(false).assertEqual(true);
    } catch (e) {
      expect(true).assertEqual(true);
    }
  });
});

8.3 �_异常测试

// 异常测试
describe('异常处理', () => {
  it('catch 捕获任务异常', async () => {
    try {
      await taskpool.execute(riskyCompute, 100, 0);
      expect(false).assertEqual(true);
    } catch (e) {
      expect(true).assertEqual(true);
    }
  });
  it('safeRun 返回 null', async () => {
    const result = await safeRun();
    expect(result).assertEqual(null);
  });
});

8.4 序列化测试

// 序列化测试
describe('序列化', () => {
  it('number 数组可序列化', async () => {
    const result: number = await taskpool.execute(sumArray, [1, 2, 3]) as number;
    expect(result).assertEqual(6);
  });
  it('函数参数不可序列化', async () => {
    try {
      await taskpool.execute(applyFunc, [1, 2], (n: number) => n * 2);
      expect(false).assertEqual(true);
    } catch (e) {
      expect(true).assertEqual(true);
    }
  });
});

九、Bug 案例

9.1 漏 @Concurrent

// 错误:漏 @Concurrent,运行时报错
function computeWrong(base: number, m: number): number { return base * m; }
await taskpool.execute(computeWrong, 100, 2);

修复:加 @Concurrent。

9.2 漏 await

// 错误:漏 await,拿到 Promise 而非结果
const result = taskpool.execute(task);   // result 是 Promise
console.log(`${result}`);   // [object Promise]

修复:加 await。

9.3 传非序列化

// 错误:传函数参数,序列化失败
await taskpool.execute(applyFunc, [1, 2], (n) => n * 2);

修复:用枚举替代。

9.4 未 catch

// 错误:未 catch,任务静默失败
await taskpool.execute(riskyCompute, 100, 0);
// → rejection 未处理

修复:包 try-catch。

提示:TaskPool 四件套:@Concurrent 装饰、await 等待、序列化参数、try-catch 异常,缺一即出错。

十、总结

10.1 核心要点

  1. @Concurrent 必需:标记可在子线程执行的函数,漏即运行时报错
  2. await 必需:漏 await 拿 Promise 而非结果
  3. 序列化参数:number/string/Array 可,函数/非序列化对象不可
  4. try-catch 异常:任务异常需 catch,否则静默失败
  5. cancel 取消:持 Task 引用可 cancel,超时用 Promise.race

10.2 性能数据回顾

场景 �_主线程影响 �_子线程耗时 备注
15×15 同步 0 18 ms �_子线程
30×30 同步 0 95 ms �_子线程
100×100 同步 0 520 ms �_子线程

10.3 下一篇预告

下一篇将深入 @Builder 的全局定义和参数传递,讲 ArkUI 自定义构建块装饰器,与本文异步加载 UI 紧密衔接。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐