6-8.【多线程】如何避免“假异步”导致的性能问题?如何判断任务是否真的在子线程?如何用工具分析线程使用?

4 阅读3分钟

在 ArkTS 开发中,“假异步”是一个隐蔽的性能杀手。它指的是代码虽然写成了 async/await 形式,但其实际逻辑仍然在 主线程(UI 线程) 上同步执行,从而导致界面卡死。


1. 如何避免“假异步”导致的性能问题?

什么是“假异步”?

在 ArkTS 中,仅仅给函数加上 async 关键字并不会自动开启新线程。async/await 只是改变了代码的执行顺序(协程化),它依然运行在调用它的那个线程上。

  • 典型错误示范

    TypeScript

    async function computeTask() {
      // 这是一个循环 1 亿次的同步计算
      let sum = 0;
      for (let i = 0; i < 100000000; i++) { sum += i; } 
      return sum;
    }
    
    // 在 UI 线程调用
    this.computeTask().then(res => { ... }); // 界面依然会卡死,因为循环是在主线程跑的
    

如何实现“真异步”?

要真正释放主线程,必须将任务物理搬运到其他线程:

  1. 使用 taskpool.execute() :将纯计算逻辑封装在 @Concurrent 函数中。
  2. 使用 worker.postMessage() :将长生命周期任务交给 Worker。
  3. 使用系统提供的异步 I/O:鸿蒙系统的 fs.readhttp.request 等内置 API 底层由 C++ 线程池实现,它们是真正的非阻塞异步。

2. 如何判断任务是否真的在子线程?

你可以通过以下三种简单方法进行实时验证:

A. 打印线程 ID(最直接)

使用 process.tid(Thread ID)在逻辑开始处打印。如果 ID 与主界面启动时打印的一致,说明任务在主线程。

TypeScript

import process from '@ohos.process';
console.info("Current Thread ID: " + process.tid);

B. 观察 UI 交互

在执行任务的同时,尝试点击界面上的按钮或滑动列表。

  • 真异步:界面依然丝滑,响应灵敏。
  • 假异步:界面完全失去响应,直到任务结束。

C. 故意制造阻塞测试

在任务中加入一个长达 5 秒的同步死循环。如果是真异步,DevEco Studio 的日志窗口会持续滚动,且手机界面不黑屏。


3. 如何用工具分析线程使用?

DevEco Studio 提供的 Profiler 是分析线程占用的“显微镜”。

A. 使用 CPU Profiler (重点)

  1. 在 DevEco Studio 下方打开 Profiler,选择 CPU 录制。

  2. 选择 Time Line 视图。

  3. 观察线程条

    • Main Thread:这是 UI 线程。如果你发现这里出现了长段的绿色(Running)或红色(Jank)条块,说明有耗时任务阻塞了 UI。
    • TaskPool / Worker Thread:如果你提交了任务,下方会出现对应的子线程条。如果子线程条在波动而主线程是空白/轻量跳动,说明异步成功。

B. 查看 Trace 轨迹

通过录制一段 Trace(.htrace 文件),可以清晰看到任务在不同线程间的“跳转”过程:

  • 查找 js_task_execute 标签,看它所属的线程名称。
  • 分析 Total Duration,如果主线程的任务耗时超过 16ms,系统会自动标记为红色警告。

总结:性能防御 Checklist

检查项验证手段优化方案
是否阻塞主线程查看 Profiler 的 Main Thread 占用。将逻辑移入 TaskPool
任务是否运行在子线程打印 process.tid确保使用了 @Concurrent 装饰器。
线程切换频率观察 Trace 中的任务切换点。合并细碎任务,减少跨线程通信频率。
异步是否生效检查是否误用了同步 API(如 fs.readSync)。全部替换为 Promise 版本的异步接口。

架构师建议:

永远保持对主线程的“敬畏”。如果你不确定一段代码是否耗时,默认把它当成耗时的,并使用 TaskPool 封装。