协程深度解析:明明只有 8 个异步任务,为何 App 线程数瞬间突破 一倍?

在 Kotlin 协程中,有一个非常隐蔽但真实存在的问题:

你只是加了 Dispatchers.IO,线程数却变多了,多的不是一点点,而是成倍增长

更关键的是:

👉 这个问题在 Application 启动阶段,比 ViewModel 更严重


0. 先讲清楚"业务真实场景"

为了让问题更贴近真实工程,我们假设一个典型 App:


App 启动 & UI 初始化行为

整个应用在启动时,会同时触发两类并发任务:

① Application 启动阶段(全局初始化)

text 复制代码
启动 App → 自动触发 4 个并发初始化任务

例如:

  • 数据库初始化
  • SDK 初始化
  • 配置加载
  • 日志系统初始化

② UI 页面启动阶段(业务并发)

text 复制代码
进入首页 → ViewModel 自动触发 4 个并发请求

例如:

  • 用户信息
  • 首页列表
  • 推荐流
  • 未读消息

👉 两个阶段一共:8 个并发任务


我们用统一任务模型模拟

为了简化问题,我们定义一个任务执行器:

kotlin 复制代码
object TaskRunner {

    private val executor = Executors.newFixedThreadPool(10) {
        Thread(it, "TaskRunner-Pool")
    }

    suspend fun test(time: Long): String =
        withContext(executor.asCoroutineDispatcher()) {
            Thread.sleep(time) // 模拟真实 IO
            "done on ${Thread.currentThread().name}"
        }
}

关键点

✔ TaskRunner 已经自带线程池 ✔ 已经是完整执行模型 ✔ 已经"会自己跑"

这是模拟 网络请求的耗时任务,比如Retrofit.

1. ViewModel 场景:UI 并发请求

首页 ViewModel:

kotlin 复制代码
viewModelScope.launch {
    launch(Dispatchers.IO) {
        repeat(4) {
            launch {
                TaskRunner.test(1000)
            }
        }
    }
}

线程现象

text 复制代码
DefaultDispatcher-worker-1 | TIMED_WAITING
DefaultDispatcher-worker-2 | TIMED_WAITING
DefaultDispatcher-worker-3 | TIMED_WAITING
DefaultDispatcher-worker-4 | TIMED_WAITING

TaskRunner-Pool-1           | WAITING
TaskRunner-Pool-2           | WAITING

🔍 执行链路

text 复制代码
Main
 → Dispatchers.IO(调度层)
   → Default Dispatcher(中转层)
     → TaskRunner Pool(真正执行)

问题本质

IO 在这里没有做任何业务执行,只做了三件事:

  • 转发任务
  • 唤醒 worker
  • 切换上下文

👉 但真正干活的是 TaskRunner


2. Application 场景:问题开始放大(关键)

App 启动时:

kotlin 复制代码
class App : Application() {
    val appScope = CoroutineScope(SupervisorJob())
    override fun onCreate() {
        super.onCreate()
        appScope.launch(Dispatchers.IO) {
            repeat(4) {
                launch {
                    TaskRunner.test(1000)
                }
            }
        }
    }
}

Application 阶段发生了什么?

启动 App 的瞬间:

text 复制代码
系统自动触发:
→ 初始化 4 个并发任务

Application 线程现象

text 复制代码
DefaultDispatcher-worker-1 | TIMED_WAITING
DefaultDispatcher-worker-2 | TIMED_WAITING
DefaultDispatcher-worker-3 | TIMED_WAITING
DefaultDispatcher-worker-4 | TIMED_WAITING

TaskRunner-Pool-1           | WAITING
TaskRunner-Pool-2           | WAITING

3. 关键差异:为什么 Application 更"炸"?


✔ 特点 1:没有 UI 节奏控制

ViewModel:

  • 页面触发
  • 用户行为驱动
  • 有生命周期 cancel

Application:

  • 一次性启动
  • 无节流
  • 全局 Scope

👉 **所有任务瞬间发射 **


✔ 特点 2:初始化任务天然 IO 密集

典型 App 初始化:

  • DB init
  • SDK init
  • config load
  • log system
  • network

开发者第一反应:

kotlin 复制代码
Dispatchers.IO

✔ 特点 3:任务嵌套更深

kotlin 复制代码
launch(Dispatchers.IO) {
    initA()
    initB()
    initC()
}

每个 init 可能还会:

  • 再 launch(IO)
  • 再 withContext(IO)
  • 再 collect Flow

👉 Application 直接形成:

text 复制代码
IO → IO → IO → Executor

4. 核心问题:调度冗余

真正问题不是线程多,而是:

❗ 同一个任务被多个 Dispatcher 重复"搬运"


❌ 错误认知

text 复制代码
Dispatchers.IO = 提升性能

❌ 实际发生

text 复制代码
launch(IO)
 → launch(Default)
   → withContext(executor)

👉 结果:

  • 3 层调度
  • 多次线程切换
  • worker 空转

5. 最关键现象:大量"不会干活的线程"

你会看到:

text 复制代码
DefaultDispatcher-worker-1 | TIMED_WAITING
DefaultDispatcher-worker-2 | TIMED_WAITING
DefaultDispatcher-worker-3 | TIMED_WAITING

❗ 这些线程在干嘛?

答案是:

👉 在"搬运任务",而不是执行任务


6. ViewModel vs Application 对比

维度 ViewModel Application
生命周期 短 长
并发方式 分散触发 一次爆发
IO 使用 局部 全局
控制能力 cancel 无 cancel
线程风险 中 高

👉 Application 本质:

没有节奏,只有洪峰


7. 🔥 本质总结

真正的问题不是:

❌ 并发太多

而是:

❗ 你让一个已经具备执行能力的系统,被 Dispatchers.IO 再调度了一次


❌ 典型反模式

kotlin 复制代码
launch(Dispatchers.IO) {
    TaskRunner.test()
}

但 TaskRunner 本身已经:

kotlin 复制代码
withContext(executor)

👉 等价于:

IO 再调度 IO 已经调度好的任务


8. ✔ 正确原则


✔ 原则 1:Application 不要统一 IO 化

kotlin 复制代码
appScope.launch {
    TaskRunner.test()
}

✔ 原则 2:IO 只用于"阻塞点"

kotlin 复制代码
withContext(Dispatchers.IO) {
    File.read()
}

✔ 原则 3:避免 IO 套 IO

text 复制代码
❌ IO → IO → Executor

✔ 原则 4:执行模型应该下沉到 Data 层

kotlin 复制代码
suspend fun test() =
    withContext(executor) { }

9. 结论

👉 当底层已经是"异步执行模型"时,上层scope的线程调度应该被束缚在一个小范围

源码: ScopeSample

这个是源码里的场景

这是去除在viewModel 里去除

scss 复制代码
launch(Dispatchers.IO)

和 Application 里换成

scss 复制代码
private val applicationScope = CoroutineScope(SupervisorJob() + appExecutor.asCoroutineDispatcher())

的场景

相关推荐
美狐美颜SDK开放平台3 小时前
直播APP平台搭建需要哪些技术?视频美颜SDK只是其中一环
android·ios·音视频·直播美颜sdk·视频美颜sdk
AIsoft_86894 小时前
传统记录方式为什么越来越难满足需求?
android·iphone
小π军4 小时前
安卓、iOS、鸿蒙三端的理解
android·ios·harmonyos
BD_Marathon5 小时前
ChatPromptTemplate的高级特性
android·java·javascript
夜晚回家5 小时前
Python 变量入门详解 | 用 BMI 案例串起赋值、乘方与取整
android·python
程序猿追5 小时前
立方体转到九十度:用 kotlin-math 核对鸿蒙上的两种旋转
android·开发语言·kotlin
半调子的社畜5 小时前
记账 App 我装了删、删了装,最后自己写了一个:本地语音识别 + R8 踩坑复盘
android·ai编程
hunterandroid6 小时前
Android 运行时权限实战:从声明权限到拒绝后的可用降级
android
a5986526 小时前
docker-android 6080 端口访问失败?
android·docker·容器·云手机
天意__7 小时前
Android手机上部署Linux服务器 在Termux里安装Ubuntu
android·linux·服务器