Skip to content

UI 线程与工作线程

理解主线程职责、子线程限制,以及如何安全地在 UI 和工作线程间传递数据。


1. UI 线程(主线程)职责

1.1 主线程职责

UI 线程负责:
├─ UI 渲染(刷新屏幕)
├─ 事件响应(点击、触摸、滚动)
├─ 生命周期回调
├─ 状态更新(@State 变化触发)
└─ 动画执行

1.2 主线程卡顿原因

❌ 绝对不能在主线程做:
├─ 网络请求(耗时几百毫秒)
├─ 文件 I/O(读/写文件)
├─ 大量 JSON 解析
├─ 复杂计算(加密、图像处理)
├─ 数据库查询(大量数据)
├─ 图片加载/解码
└─ 同步睡眠(Thread.sleep)

后果:
├─ 卡顿(>16ms 掉帧)
├─ ANR(Application Not Responding)
└─ 用户体验极差

1.3 卡顿检测

typescript
// 主线程耗时检测
let start = Date.now()

// 耗时操作(错误示范)
heavyOperation()

let duration = Date.now() - start
if (duration > 100) {
    console.warn('主线程耗时过长:', duration + 'ms')
}

// 正确做法:用 TaskPool
TaskPool.execute(() => heavyOperation())
    .then(() => {
        // 结果处理(在子线程)
        context.getMainContext().syncDispatch(() => {
            this.data = result  // 回到主线程更新 UI
        })
    })

2. 工作线程

2.1 工作线程分类

线程类型适用场景管理方式
TaskPool 线程短时任务(< 500ms)系统自动
Worker 线程长时任务(> 500ms)手动管理
原生线程特殊场景手动管理

2.2 工作线程与 UI 线程通信

typescript
// 工作线程处理
TaskPool.execute(() => {
    // 1. 处理数据
    let data = loadData()
    
    // 2. 回到主线程更新 UI
    context.getMainContext().syncDispatch(() => {
        // 修改 @State
        this.data = data
        this.isLoading = false
    })
    
    return data
}).then((result) => {
    // TaskPool 的 then 也在主线程执行
    console.log('结果:', result)
})

3. @State 线程安全

3.1 @State 只能在 UI 线程修改

typescript
@Component
struct MyComponent {
    @State count: number = 0

    // ❌ 错误:子线程修改 @State
    wrongMethod() {
        TaskPool.execute(() => {
            this.count++  // ❌ 运行时错误
        })
    }

    // ✅ 正确:子线程处理,UI 线程更新
    correctMethod() {
        TaskPool.execute(() => {
            let result = heavyComputation()
            
            context.getMainContext().syncDispatch(() => {
                this.count = result  // ✅ 在主线程更新
            })
        })
    }
}

3.2 线程安全的状态传递

typescript
// 工作线程 → UI 线程(通过序列化传递)
TaskPool.execute((data: WorkerData): SerializedData => {
    return serialize(data)  // 自动序列化
}).then((result: SerializedData) => {
    // 回到主线程,结果已反序列化
    this.data = result
})

4. 线程模型对比

4.1 Android vs HarmonyOS

Android 线程模型                      HarmonyOS 线程模型
┌─────────────┐                      ┌──────────────┐
│  Main Thread │                      │  UI 线程      │
│  (Looper)    │                      │              │
├─────────────┤                      ├──────────────┤
│  Worker      │     共享内存          │  Worker      │  消息传递
│  Thread      │──↕── 同步            │  Thread      │  序列化
├─────────────┤                      ├──────────────┤
│  TaskThread  │                      │  TaskPool    │  自动管理
│  (Executor)  │                      │  Thread      │
└─────────────┘                      └──────────────┘

核心差异:
├─ Android:共享内存 + 同步
└─ 鸿蒙:内存隔离 + 消息传递

5. 性能优化

5.1 减少线程切换

❌ 频繁切换:
├─ 子线程 → 主线程 → 子线程 → 主线程(来回切换)
└─ 开销大,性能差

✅ 合并处理:
├─ 子线程处理完所有数据
└─ 一次回到主线程更新 UI

5.2 线程池复用

typescript
// ❌ 错误:每次创建新线程
for (let i = 0; i < 100; i++) {
    TaskPool.execute(() => { ... })
}

// ✅ 正确:复用已有线程(TaskPool 自动实现)
TaskPool.execute(() => { ... })
TaskPool.execute(() => { ... })
// TaskPool 自动复用线程

6. 面试高频考点

Q1: 主线程卡死的常见原因?

回答:在 aboutToAppear 或点击事件中进行同步 I/O 操作、复杂 JSON 解析或大量计算。解决方法:用 TaskPool 放后台、async/await。

Q2: @State 能在子线程修改吗?

回答:不可以。@State 必须在 UI 线程修改。子线程处理完后通过 context.getMainContext().syncDispatch() 回到主线程更新。


🐱 小猫提示:线程模型记住 "主线程管 UI、子线程做事、通过 syncDispatch 回主线程更新 @State"