打破主线程的桎梏,HarmonyOS 多线程与大数据流转重构

上周的版本更新中,我们加入了一个离线数据大屏功能。测试同学在低端机上一点开大屏页面,整个应用瞬间卡死,紧接着弹出了系统级的应用无响应弹窗。分析内存和 CPU 占用后发现,一个 8MB 的本地 JSON 文件在主线程被强行同步解析,直接堵塞了渲染管线。这次事故逼着我把整个数据处理链路迁移到了 TaskPool 并发体系中。

告别伪异步的假象

以前我总以为 async/await 就是多线程,直到这次崩溃才明白,如果没有使用独立的线程池,所有的计算依然在 UI 线程排队。ArkTS 采用了类似于 Actor 的内存隔离模型,线程之间不能共享内存。这意味着,你无法像传统 Java 那样在子线程里直接修改主线程的变量。

TaskPool 的轻量级并发

对于这种短平快的解析任务,TaskPool 是最佳选择。我们将解析逻辑包装成一个带有 @Concurrent 装饰器的全局函数。

typescript

复制代码
import taskpool from '@ohos.taskpool';

@Concurrent
function parseHugeJson(jsonStr: string): any[] {
  const data = JSON.parse(jsonStr);
  return data.list; 
}

注意:这个带有装饰器的函数绝对不能有闭包,不能引用任何外部变量或 this 指向。它只能完全依赖传入的参数。执行后,把结果通过 return 抛回主线程,主线程拿到数据后再更新 UI。

深拷贝带来的内存浩劫

用上 TaskPool 后,界面确实不卡了,但大屏页面频繁切换时,内存直接飙到了 1GB 以上。

这里藏着一个极深的警示:主线程传给子线程的参数,以及子线程返回给主线程的结果,底层都会发生一次深拷贝 。一个 8MB 的字符串,传给子线程时复制一份,解析成对象后再复制一份传回来。大内存的反复读写导致了溢出。

为了解决这个问题,对于大块的数据,必须使用 SharedArrayBuffer 共享内存,或者在创建任务时使用 Transferable 转移所有权。

常驻后台的 Worker 战士

如果处理完大屏数据后,还要维持一个长连接或者持续的音频解码,TaskPool 就不合适了,因为任务结束后线程可能会被回收。这时候必须请出 Worker。

初始化 Worker 时,必须注意文件路径的准确性。在 Stage 模型下,路径必须严格遵循 entry/ets/workers/xxx.ets 的格式。一旦拼写错误,编译阶段就直接报错。并且在页面销毁时,必须手动调用 terminate() 关闭它,否则后台线程会一直泄露。

写一个简单的斐波那契数列计算,把数值设定为 45 左右。先放在主线程跑,观察 UI 冻结的时长。然后用 TaskPool 包装,对比一下两边的执行体验,你会对并发编程的威力有直观感受。