HarmonyOS7 TaskPool 多线程:耗时计算不卡 UI
文章目录
- 前言
- 为什么不能主线程算
- TaskPool 基本用法
- 代码实现
- 派发任务并拿结果
- 取消任务 & 任务组
- 核心代码
- TaskPool vs Worker 怎么选
- 常见误区(小白必踩)
- 完整可运行示例:TaskPool 算质数
- 完整示例
- 写在最后
前言
你在主线程算个加密、解个压、处理几万条数据,界面就"冻"住了——点不动、滑不了,用户以为 app 死了。
HarmonyOS 提供了TaskPool(任务池)把这种重活丢到后台线程,主线程专心管 UI。这篇文章讲清怎么用 TaskPool 跑耗时任务,还顺带提一下和Worker的区别。
为什么不能主线程算
主线程(UI 线程)负责响应用户操作、刷新界面。你塞个 3 秒的循环进去,这 3 秒内它啥也干不了——界面就卡死。
解法:把重活扔到后台线程,算完了把结果交回主线程。
TaskPool 基本用法
TaskPool 适合"跑一次、短任务、要结果"的场景。先定义一个能被序列化的任务函数:
代码实现
// 任务函数:必须是顶层函数,参数和返回值要可序列化functionheavyCalc(taskParam:number):number{letsum=0for(leti=0;i<taskParam;i++){sum+=Math.sqrt(i)*Math.sin(i)// 假装很耗时}returnsum}要点:
- 任务函数要放在模块顶层(不能在组件类里)。
- 参数和返回值必须是可序列化类型(number/string/对象等),不能传函数或闭包。
派发任务并拿结果
import{taskpool}from'@kit.ExecutionKit'asyncfunctionrunTask(){consttask=newtaskpool.Task(heavyCalc,1000000)// 包装成任务constresult=awaittaskpool.execute(task)// 派发执行console.info('计算结果:'+result)// 主线程拿到结果}new taskpool.Task(fn, arg):把函数和参数包成任务。taskpool.execute(task):丢进线程池,返回 Promise,算完拿结果。- 整个过程主线程不阻塞,UI 照常响应。
execute是 Promise,用await等结果,或者用.then()。界面不会因为后台在算而卡住。
取消任务 & 任务组
长时间任务可以取消,多个任务可以分组:
核心代码
// 取消consttask=newtaskpool.Task(heavyCalc,9999999)constpromise=taskpool.execute(task)taskpool.cancel(task)// 不需要了就取消// 任务组:等所有完成constgroup=newtaskpool.TaskGroup()group.addTask(newtaskpool.Task(heavyCalc,1000))group.addTask(newtaskpool.Task(heavyCalc,2000))awaittaskpool.execute(group)TaskPool vs Worker 怎么选
| 维度 | TaskPool | Worker |
|---|---|---|
| 适用 | 短任务、跑完即弃 | 长驻、常通信 |
| 通信 | 只传最终结果 | 可双向持续通信 |
| 创建成本 | 低(池化复用) | 高(每个独立线程) |
| 典型场景 | 单次计算、加解密 | 持续监听、长连接处理 |
经验:单次重计算用 TaskPool;需要长期驻扎、反复通信的后台逻辑(如音视频处理线程)用 Worker。
常见误区(小白必踩)
| 误区 | 说明 |
|---|---|
| 任务函数写在组件类里 | TaskPool 任务必须是模块顶层函数,写在类里序列化失败。 |
| 参数不可序列化 | 传函数、闭包会报错,参数返回值用 number/string/普通对象。 |
| 重活在主线程硬扛 | 把循环、解析丢进 TaskPool,主线程才不卡。 |
下面这段代码可以直接复制到 DevEco Studio 里运行。建议你边读边敲,改一改文末「动手改一改」里的参数,亲眼看看效果。
完整可运行示例:TaskPool 算质数
把耗时计算丢到线程池,UI 不卡。
完整示例
// TaskPool 执行耗时任务,结果回主线程import{taskpool}from'@kit.ArkTS'@ConcurrentfunctioncountPrimes(max:number):number{letcnt=0for(leti=2;i<max;i++){letp=truefor(letj=2;j*j<=i;j++){if(i%j===0){p=false;break}}if(p)cnt++}returncnt}@Entry@Componentstruct Calc{@Stateresult:string='点按钮开始计算'build(){Column({space:16}){Text(this.result).fontSize(18)Button('算 1~200000 的质数').onClick(async()=>{consttask=newtaskpool.Task(countPrimes,200000)constn=awaittaskpool.execute(task)// 不阻塞 UIthis.result='共有 '+n+' 个质数'})}.padding(20)}}你会看到什么:点按钮后,即使计算要几百毫秒,界面依然能拖动、点击不卡顿;算完结果自动回到主线程更新文字。
动手改一改:
- 把
200000改成1000000,对比 UI 是否有感卡顿(应该没有)。 - 用
taskpool.execute(task, taskpool.Priority.HIGH)提优先级。 - 把
@Concurrent函数拆成多段,用taskpool并行多个任务。
写在最后
TaskPool 是"把重活丢后台"的首选:定义顶层任务函数 →new Task包装 →execute派发 →await拿结果。主线程全程不卡,用户体验直接起飞。
下次你写循环、解析、计算前先问一句:"这活重吗?"重的话,丢进 TaskPool,别在主线程硬扛。
