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多线程开发需要注意以下几个关键点:
- 合理选择数据传递方式:小数据直接返回,大数据使用Sendable对象,二进制数据使用SharedArrayBuffer
- 正确处理上下文依赖:数据库和首选项访问需要传递context对象
- 合理组织代码结构:工具类通过import导入使用
- 理解线程隔离特性:无法直接修改主线程变量,需要通过返回值传递数据
- 避免异步嵌套:多线程任务中保持同步执行,异步操作在主线程处理
通过遵循这些最佳实践,可以充分发挥TaskPool的优势,提升应用的性能和用户体验。