鸿蒙TaskPool多线程开发指南

1. TaskPool多线程基本使用

1.1 TaskPool简介

  • TaskPool是鸿蒙系统提供的线程池管理机制,用于执行耗时任务而不阻塞主线程。它可以自动管理线程的创建、销毁和复用,提供了比Worker更轻量级的多线程解决方案。

1.2 基本使用步骤

导入模块

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

创建并执行任务

ts 复制代码
// 定义任务函数
@Concurrent
function calculateSum(start: number, end: number): number {
  let sum = 0;
  for (let i = start; i <= end; i++) {
    sum += i;
  }
  return sum;
}

// 在主线程中调用
async function executeTask() {
  try {
    // 创建任务
    const task = new taskpool.Task(calculateSum, 1, 10000);
    
    // 执行任务
    const result = await taskpool.execute(task);
    console.log('计算结果:', result);
  } catch (error) {
    console.error('任务执行失败:', error);
  }
}

批量执行任务

ts 复制代码
async function executeBatchTasks() {
  const tasks = [
    new taskpool.Task(calculateSum, 1, 1000),
    new taskpool.Task(calculateSum, 1001, 2000),
    new taskpool.Task(calculateSum, 2001, 3000)
  ];
  
  try {
    const results = await taskpool.execute(tasks);
    console.log('所有任务结果:', results);
  } catch (error) {
    console.error('批量任务执行失败:', error);
  }
}

2. 多线程数据传递机制

2.1 基本类型数据传递(少量数据)

  • 对于基本数据类型(number、string、boolean等)和简单对象,可以直接通过return返回结果。
ts 复制代码
@Concurrent
function processBasicData(num: number, str: string, flag: boolean): object {
  return {
    processedNum: num * 2,
    processedStr: str.toUpperCase(),
    processedFlag: !flag,
    timestamp: Date.now()
  };
}

// 使用示例
async function handleBasicData() {
  const task = new taskpool.Task(processBasicData, 42, "hello", true);
  const result = await taskpool.execute(task);
  console.log('处理结果:', result);
}

2.2 大容量数据使用Sendable对象传递

  • 对于大量数据,推荐使用Sendable对象来提高传递效率并避免数据拷贝。
  • Sendable 文档
ts 复制代码
import collections from "@arkts.collections";
// 定义Sendable类
@Sendable
class DataContainer {
  public data: collections.Map<string, number> = new collections.Map<string, number>();
  public metadata: string;

  constructor(data: collections.Map<string, number>, metadata: string) {
    this.data = data;
    this.metadata = metadata;
  }

  public getSize(): number {
    return this.data.size;
  }
}

@Concurrent 
function processLargeData(container: DataContainer): DataContainer {
  // 处理大量数据 - 使用Map的forEach遍历和新Map存储
  const processedData = new collections.Map<string, number>();
  
  container.data.forEach((value: number, key: string) => {
    processedData.set(key, value * 2);
  });
  
  return new DataContainer(processedData, `处理完成: ${container.metadata}`);
}

// 使用示例
async function handleLargeData() {
  // 创建大量数据的Map
  const largeMap = new collections.Map<string, number>();
  for (let i = 0; i < 100000; i++) {
    largeMap.set(`item_${i}`, i);
  }
  
  const container = new DataContainer(largeMap, "原始数据");
  
  const task = new taskpool.Task(processLargeData, container);
  const result = await taskpool.execute(task);
  
  console.log('处理完成,数据大小:', result.getSize());
  
  // 可选:输出一些示例数据来验证处理结果
  console.log('示例处理后的数据:');
  let count = 0;
  result.data.forEach((value: number, key: string) => {
    if (count < 5) { // 只输出前5个项目作为示例
      console.log(`${key}: ${value}`);
      count++;
    }
  });
}

2.3 文件Buffer使用SharedArrayBuffer传递

  • 对于文件buffer等二进制数据,可以转换为SharedArrayBuffer传递,避免大量数据拷贝。
  • SharedArrayBuffer 文档
ts 复制代码
@Concurrent
function processFileBuffer(sharedBuffer: SharedArrayBuffer, offset: number, length: number): SharedArrayBuffer {
  // 将SharedArrayBuffer转换为处理所需的类型
  const uint8Array = new Uint8Array(sharedBuffer, offset, length);
  
  // 创建新的SharedArrayBuffer存储处理结果
  const resultBuffer = new SharedArrayBuffer(length);
  const resultArray = new Uint8Array(resultBuffer);
  
  // 处理数据(示例:字节值加1)
  for (let i = 0; i < length; i++) {
    resultArray[i] = uint8Array[i] + 1;
  }
  
  return resultBuffer;
}

// 使用示例
async function handleFileBuffer() {
  // 模拟文件数据
  const fileData = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
  
  // 创建SharedArrayBuffer
  const sharedBuffer = new SharedArrayBuffer(fileData.length);
  const sharedArray = new Uint8Array(sharedBuffer);
  sharedArray.set(fileData);
  
  // 执行任务
  const task = new taskpool.Task(processFileBuffer, sharedBuffer, 0, fileData.length);
  const resultBuffer = await taskpool.execute(task);
  
  // 将结果转换回Uint8Array查看
  const resultArray = new Uint8Array(resultBuffer);
  console.log('处理结果:', Array.from(resultArray));
}

3. 多线程使用注意细节

3.1 数据库和首选项调用需要传递上下文

在多线程中访问数据库或首选项时,需要将上下文对象传递给子线程。

ts 复制代码
import { preferences } from '@kit.ArkData';
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';

@Concurrent
function accessDatabase(context: common.UIAbilityContext, tableName: string): Promise<void> {
  return new Promise<void>((resolve, reject) => {
    // 使用传递的context访问数据库
    const config: relationalStore.StoreConfig = {
      name: 'mydb.db',
      securityLevel: relationalStore.SecurityLevel.S1
    };
    
    relationalStore.getRdbStore(context, config)
      .then(store => {
        // 执行数据库操作
        return store.query(`SELECT * FROM ${tableName}`);
      })
      .then(resultSet => {
        console.log('查询结果行数:', resultSet.rowCount);
        resolve();
      })
      .catch(error => {
        reject(error);
      });
  });
}

@Concurrent
function accessPreferences(context: common.UIAbilityContext, key: string): Promise<string> {
  return new Promise<string>((resolve, reject) => {
    // 使用传递的context访问首选项
    preferences.getPreferences(context, 'myPrefs')
      .then(prefs => {
        return prefs.get(key, '');
      })
      .then(value => {
        resolve(value as string);
      })
      .catch(error => {
        reject(error);
      });
  });
}

// 使用示例
async function useContextInTask(context: common.UIAbilityContext) {
  const dbTask = new taskpool.Task(accessDatabase, context, 'users');
  const prefsTask = new taskpool.Task(accessPreferences, context, 'username');
  
  try {
    await taskpool.execute(dbTask);
    const username = await taskpool.execute(prefsTask);
    console.log('用户名:', username);
  } catch (error) {
    console.error('访问失败:', error);
  }
}

3.2 静态方法通过import导入使用

在多线程中使用工具类的静态方法时,需要通过import导入。

ts 复制代码
// utils.ts - 工具类文件
export class MathUtils {
  static factorial(n: number): number {
    if (n <= 1) return 1;
    return n * MathUtils.factorial(n - 1);
  }
  
  static isPrime(num: number): boolean {
    if (num <= 1) return false;
    for (let i = 2; i <= Math.sqrt(num); i++) {
      if (num % i === 0) return false;
    }
    return true;
  }
}

// main.ts - 主文件
import { MathUtils } from './utils';

@Concurrent
function calculateFactorial(n: number): number {
  // 在多线程中导入并使用静态方法
  return MathUtils.factorial(n);
}

@Concurrent
function findPrimes(start: number, end: number): number[] {
  const primes: number[] = [];
  for (let i = start; i <= end; i++) {
    if (MathUtils.isPrime(i)) {
      primes.push(i);
    }
  }
  return primes;
}

// 使用示例
async function useStaticMethods() {
  const factorialTask = new taskpool.Task(calculateFactorial, 10);
  const primesTask = new taskpool.Task(findPrimes, 1, 100);
  
  const [factorialResult, primesResult] = await taskpool.execute([factorialTask, primesTask]);
  console.log('10的阶乘:', factorialResult);
  console.log('1-100的素数:', primesResult);
}

3.3 无法在多线程中改变主线程变量值

多线程无法直接修改主线程中的变量,需要通过返回值或其他机制传递数据。

ts 复制代码
// ❌ 错误做法:尝试在多线程中修改主线程变量
let globalCounter = 0;

@Concurrent
function incorrectIncrement(): void {
  // 这样做无效,无法修改主线程的globalCounter
  globalCounter++; // 不会影响主线程的变量
}

// ✅ 正确做法:通过返回值传递修改后的数据
@Concurrent
function correctIncrement(currentValue: number): number {
  return currentValue + 1;
}

// 使用示例
async function handleCounterUpdate() {
  let counter = 0;
  
  // 错误的尝试
  const incorrectTask = new taskpool.Task(incorrectIncrement);
  await taskpool.execute(incorrectTask);
  console.log('错误方式后的计数器:', counter); // 仍然是0
  
  // 正确的方式
  const correctTask = new taskpool.Task(correctIncrement, counter);
  counter = await taskpool.execute(correctTask);
  console.log('正确方式后的计数器:', counter); // 现在是1
}

3.4 多线程中需要同步执行,避免异步嵌套

  • 在多线程任务中应该同步执行操作,不要将耗时任务放在多线程的异步中。
ts 复制代码
// ❌ 错误做法:在多线程中使用异步操作
@Concurrent
async function incorrectAsyncTask(data: number[]): Promise<number[]> {
  // 避免在多线程中使用异步操作
  return new Promise<number[]>((resolve) => {
    setTimeout(() => {
      const result = data.map(x => x * 2);
      resolve(result);
    }, 1000);
  });
}

// ✅ 正确做法:同步执行所有操作
@Concurrent
function correctSyncTask(data: number[]): number[] {
  // 同步处理数据
  const result = data.map(x => {
    // 模拟耗时计算(同步)
    let temp = x;
    for (let i = 0; i < 1000000; i++) {
      temp = Math.sin(temp);
    }
    return temp * 2;
  });
  return result;
}

// 如果确实需要异步操作,应该在主线程处理
async function handleAsyncOperations() {
  const data = [1, 2, 3, 4, 5];
  
  // 先执行多线程同步任务
  const syncTask = new taskpool.Task(correctSyncTask, data);
  const processedData = await taskpool.execute(syncTask);
  
  // 然后在主线程处理异步操作
  const finalResult = await new Promise<number[]>((resolve) => {
    setTimeout(() => {
      resolve(processedData.map(x => x + 1));
    }, 100);
  });
  
  console.log('最终结果:', finalResult);
}

总结

TaskPool多线程开发需要注意以下几个关键点:

  1. 合理选择数据传递方式:小数据直接返回,大数据使用Sendable对象,二进制数据使用SharedArrayBuffer
  2. 正确处理上下文依赖:数据库和首选项访问需要传递context对象
  3. 合理组织代码结构:工具类通过import导入使用
  4. 理解线程隔离特性:无法直接修改主线程变量,需要通过返回值传递数据
  5. 避免异步嵌套:多线程任务中保持同步执行,异步操作在主线程处理

通过遵循这些最佳实践,可以充分发挥TaskPool的优势,提升应用的性能和用户体验。

相关推荐
90后的晨仔9 小时前
ArkTS 语言中的number和Number区别是什么?
前端·harmonyos
二流小码农12 小时前
鸿蒙开发:CodeGenie万能卡片生成
android·ios·harmonyos
爱笑的眼睛1112 小时前
HarmonyOS 组件复用面试宝典 [特殊字符]
华为·面试·harmonyos·harmonyos next
半醉看夕阳13 小时前
HarmonyOS开发 ArkTS 之 var 、let、const 变量声明的剖析
typescript·harmonyos·arkts
一天前15 小时前
鸿蒙长列表和嵌套滑动处理
harmonyos
我睡醒再说15 小时前
HarmonyOS5 运动健康app(二):健康跑步(附代码)
华为·信息可视化·harmonyos·arkts·应用开发
ChinaDragon20 小时前
HarmonyOS:视频播放 (Video)
harmonyos
xq952720 小时前
鸿蒙next rcp网络请求工具类进阶版本来了
harmonyos
枫叶丹421 小时前
【HarmonyOS Next之旅】DevEco Studio使用指南(三十三) -> 构建任务
华为·harmonyos·deveco studio·harmonyos next