鸿蒙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的优势,提升应用的性能和用户体验。

相关推荐
zhanshuo29 分钟前
在鸿蒙里优雅地处理网络错误:从 Demo 到实战案例
harmonyos
zhanshuo31 分钟前
在鸿蒙中实现深色/浅色模式切换:从原理到可运行 Demo
harmonyos
whysqwhw6 小时前
鸿蒙分布式投屏
harmonyos
whysqwhw7 小时前
鸿蒙AVSession Kit
harmonyos
whysqwhw9 小时前
鸿蒙各种生命周期
harmonyos
whysqwhw10 小时前
鸿蒙音频编码
harmonyos
whysqwhw10 小时前
鸿蒙音频解码
harmonyos
whysqwhw10 小时前
鸿蒙视频解码
harmonyos
whysqwhw10 小时前
鸿蒙视频编码
harmonyos
ajassi200010 小时前
开源 Arkts 鸿蒙应用 开发(十八)通讯--Ble低功耗蓝牙服务器
华为·开源·harmonyos