写在前面
taskpool工具进行封装,将常用api及异步锁封装进去,减少使用的复杂度。
使用示例
// 工具类使用
ThreadUtils.runOnTaskPoolV2({
taskName: "task_name",
func: realConcurrentFunc,
args: [context,
...
],
asyncLockKey: 'async_lock_key',
uiThreadReceiveFunc: uiThreadCallbackFunc,
sequenceKey: "taskpool_sequence_key"
});
// 实际执行的方法
@Concurrent
export async function realConcurrentFunc(context: Context,...) {
...
}
// 主线程回调方法
function uiThreadCallbackFunc(...) {
...
}
参数解析
| 参数名 | 类型 | 含义 | 默认值 | 是否必须 | 注意事项 |
|---|---|---|---|---|---|
| taskName | string | 任务名,创建task时传入的变量 | '' | 否 | 非必须传 |
| func | Function | 真正执行的函数 | 无默认值 | 是 | 1.必须传,不可为空2.必须为@Concurrent |
| args | Object[] | func所需的参数, | [] | 否,根据func决定 | 1.参数必须为Sendable2.此处的参数用数组的方式传进来,真正调用func时会将参数一个个传进去。例如:入参为[1, 'hello', context]执行时为func(1, 'hello', context) |
| asyncLockKey | string | 异步锁的key | '' | 否 | 1.非必须传,如果不传或传空字符串,则不适用异步锁2.异步锁根据key来管理,需要用到同一个异步锁的传相同的key即可 |
| uiThreadReceiveFunc | Function | 主线程回调函数 | undefined | 否 | 1.非必须传,需要回调主线程时使用2.调用方式是在func中通过以下方式实现taskpool.Task.sendData(...args) |
| sequenceKey | string | task顺序执行的key | '' | 否 | 1.非必须传,需要子线程任务串行执行时使用2.负责执行task的runner,根据sequenceKey来选择;如无执行顺序要求,建议不传或传空字符串 |
流程图

代码解析
工具类方法封装
1.对传入的参数进行解析,添加默认值
2.因为可以选择是否使用异步锁,封装了一个壳方法进行加锁
3.根据用户选择的使用需要串行执行,选择taskpool or runner
/**
* 切换至工作线程执行
*
* api使用限制:
* 1.在工作线程执行的函数,必须有@Concurrent注解
* 2.在工作线程执行的函数,参数必须为可序列化的
*
* 如果待执行函数中需要向host线程回调,在task的func中,通过task.sendData(...args: Object[])向host线程发送数据,
* args为calback所需要的参数。
*
* @param taskName 任务名称
* @param func 子线程待执行方法
* @param args 子线程待执行方法的参数
* @param uiThreadReceiveFunc 主线程回调函数
* @returns
*/
public static async runOnTaskPoolV2(runOnTaskpoolData: RunOnTaskpoolData): Promise<Object | null | undefined> {
// 参数校验
const taskName: string = runOnTaskpoolData.taskName ?? '';
const func: Function = runOnTaskpoolData.func;
const args: Object[] = runOnTaskpoolData.args ?? [];
const asyncLockKey: string = runOnTaskpoolData.asyncLockKey ?? '';
const uiThreadReceiveFunc: Function | undefined = runOnTaskpoolData.uiThreadReceiveFunc;
const sequenceKey: string = runOnTaskpoolData.sequenceKey ?? '';
// 异步锁
let asyncLock: ArkTSUtils.locks.AsyncLock | null = AsyncLockManager.getInstance().getAsyncLock(asyncLockKey);
// 构建task
let bgTask = new taskpool.Task(taskName, shellFunction, [func, args, asyncLock]);
// 回调函数
if (uiThreadReceiveFunc) {
bgTask.onReceiveData(uiThreadReceiveFunc);
}
// 判断是否需要顺序执行
if (StringUtils.isNotEmpty(sequenceKey)) {
// 需要顺序执行,使用sequenceRunner
let runner: taskpool.SequenceRunner | undefined = BMThreadUtils.sequenceRunnerRecord[sequenceKey];
if (!runner) {
runner = new taskpool.SequenceRunner();
BMThreadUtils.sequenceRunnerRecord[sequenceKey] = runner;
}
return await runner.execute(bgTask);
} else {
// 不需要顺序执行,使用普通task
return await taskpool.execute(bgTask);
}
}
壳方法
工具类默认执行的方法,进行异步锁加锁处理后,执行真正的函数func,同时将args从数组参数展开传递给函数
/**
* taskpool中切换至工作线程执行的壳function
* 1.为方法执行加锁
*
* tips:
*
* taskpool执行的方法、参数有如下要求:(必须满足,否则执行时会报错)
* 1.taskpool中传入的function,必须携带@Concurrent注解
* 2.taskpool中传入的function,参数必须为可序列化的
*
* 如果你的方法中需要使用到【调用方】的方法、变量,无法在调用方中声明该方法,会爆出如下错误:
* "Only imported variablies and local variables can be used in @Concurrent decorated functions. <ArkTSCheck>"
*
* 系统组件提供了一些可序列化的方法,涉及到的尽量使用系统api:
* 1.共享用户首选项
* 2.可共享的色彩管理
* 3.基于Sendable对象的图片处理
* 4.资源管理
* 5.SendableContext对象管理
*/
@Concurrent
export async function shellFunction(shellFuncArgs: Object[]): Promise<Object | null | undefined> {
try {
// 入参
const func: Function = shellFuncArgs[0] as Function;
const args: Object[] = shellFuncArgs[1] as Object[];
const asyncLock: ArkTSUtils.locks.AsyncLock | null = shellFuncArgs[2] as ArkTSUtils.locks.AsyncLock | null;
// 实际执行的函数
const executeLogic = async () => {
return await func(...args);
};
// 如果有锁则加锁执行
if (asyncLock) {
return await asyncLock.lockAsync(async () => {
return await executeLogic();
});
} else {
return await executeLogic();
}
} catch (error) {
BMLog.error('ConcurrentFunction', `shellFunction error: code=${error.code}, message=${error.message}`);
}
return null;
}
异步锁管理
根据key来管理异步锁,互相影响的任务可传入相同的key,来保证执行顺序
/**
* 异步锁管理器
*
* 提供线程池使用的异步锁,保存在主线程中;在主线程调用时获取到,传入子线程使用
*/
export class AsyncLockManager {
/**
* 默认线程池的key
*/
public static readonly KEY_TASKPOOL_DEFAULT: string = "taskPoolDefault";
/**
* 线程池异步锁
*/
public asyncLockRecord: Record<string, ArkTSUtils.locks.AsyncLock> = {};
private static instance: AsyncLockManager;
private constructor() {}
public static getInstance(): AsyncLockManager {
if (!AsyncLockManager.instance) {
AsyncLockManager.instance = new AsyncLockManager();
}
return AsyncLockManager.instance;
}
/**
* 获取异步锁,当key穿空时返回null
* @param key 锁的key
* @returns 异步锁
*/
public getAsyncLock(key: string = AsyncLockManager.KEY_TASKPOOL_DEFAULT): ArkTSUtils.locks.AsyncLock | null {
if (BMStringUtils.isEmpty(key)) {
return null;
}
if (!this.asyncLockRecord[key]) {
this.asyncLockRecord[key] = new ArkTSUtils.locks.AsyncLock();
}
return this.asyncLockRecord[key];
}
}
task执行顺序
taskpool提供了保证task顺序执行的api,需要保证执行顺序的api可传入相同的key
/**
* 顺序执行任务taskpool
*/
private static sequenceRunnerRecord: Record<string, taskpool.SequenceRunner> = {};
// 判断是否需要顺序执行
if (BMStringUtils.isNotEmpty(sequenceKey)) {
// 需要顺序执行,使用sequenceRunner
let runner: taskpool.SequenceRunner | undefined = BMThreadUtils.sequenceRunnerRecord[sequenceKey];
if (!runner) {
runner = new taskpool.SequenceRunner();
BMThreadUtils.sequenceRunnerRecord[sequenceKey] = runner;
}
return await runner.execute(bgTask);
} else {
// 不需要顺序执行,使用普通task
return await taskpool.execute(bgTask);
}
写在后面
If you like this article, it is written by Johnny Deng.
If not, I don't know who wrote it.