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

作者:潜龙勿用之化骨龙日期:2026/7/6

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

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

更关键的是:

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


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

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


App 启动 & UI 初始化行为

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

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

1启动 App  自动触发 4 个并发初始化任务
2

例如:

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

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

1进入首页  ViewModel 自动触发 4 个并发请求
2

例如:

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

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


我们用统一任务模型模拟

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

1object TaskRunner {
2
3    private val executor = Executors.newFixedThreadPool(10) {
4        Thread(it, "TaskRunner-Pool")
5    }
6
7    suspend fun test(time: Long): String =
8        withContext(executor.asCoroutineDispatcher()) {
9            Thread.sleep(time) // 模拟真实 IO
10            "done on ${Thread.currentThread().name}"
11        }
12}
13

关键点

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

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

1. ViewModel 场景:UI 并发请求

首页 ViewModel:

1viewModelScope.launch {
2    launch(Dispatchers.IO) {
3        repeat(4) {
4            launch {
5                TaskRunner.test(1000)
6            }
7        }
8    }
9}
10

线程现象

1DefaultDispatcher-worker-1 | TIMED_WAITING
2DefaultDispatcher-worker-2 | TIMED_WAITING
3DefaultDispatcher-worker-3 | TIMED_WAITING
4DefaultDispatcher-worker-4 | TIMED_WAITING
5
6TaskRunner-Pool-1           | WAITING
7TaskRunner-Pool-2           | WAITING
8

🔍 执行链路

1Main
2  Dispatchers.IO(调度层)
3    Default Dispatcher(中转层)
4      TaskRunner Pool(真正执行)
5

问题本质

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

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

👉 但真正干活的是 TaskRunner


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

App 启动时:

1class App : Application() {
2    val appScope = CoroutineScope(SupervisorJob())
3    override fun onCreate() {
4        super.onCreate()
5        appScope.launch(Dispatchers.IO) {
6            repeat(4) {
7                launch {
8                    TaskRunner.test(1000)
9                }
10            }
11        }
12    }
13}
14

Application 阶段发生了什么?

启动 App 的瞬间:

1系统自动触发:
2 初始化 4 个并发任务
3

Application 线程现象

1DefaultDispatcher-worker-1 | TIMED_WAITING
2DefaultDispatcher-worker-2 | TIMED_WAITING
3DefaultDispatcher-worker-3 | TIMED_WAITING
4DefaultDispatcher-worker-4 | TIMED_WAITING
5
6TaskRunner-Pool-1           | WAITING
7TaskRunner-Pool-2           | WAITING
8

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


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

ViewModel:

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

Application:

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

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


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

典型 App 初始化:

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

开发者第一反应:

1Dispatchers.IO
2

✔ 特点 3:任务嵌套更深

1launch(Dispatchers.IO) {
2    initA()
3    initB()
4    initC()
5}
6

每个 init 可能还会:

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

👉 Application 直接形成:

1IO  IO  IO  Executor
2

4. 核心问题:调度冗余

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

同一个任务被多个 Dispatcher 重复“搬运”


❌ 错误认知

1Dispatchers.IO = 提升性能
2

❌ 实际发生

1launch(IO)
2  launch(Default)
3    withContext(executor)
4

👉 结果:

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

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

你会看到:

1DefaultDispatcher-worker-1 | TIMED_WAITING
2DefaultDispatcher-worker-2 | TIMED_WAITING
3DefaultDispatcher-worker-3 | TIMED_WAITING
4

❗ 这些线程在干嘛?

答案是:

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


6. ViewModel vs Application 对比

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

👉 Application 本质:

没有节奏,只有洪峰


7. 🔥 本质总结

真正的问题不是:

❌ 并发太多

而是:

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


❌ 典型反模式

1launch(Dispatchers.IO) {
2    TaskRunner.test()
3}
4

但 TaskRunner 本身已经:

1withContext(executor)
2

👉 等价于:

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


8. ✔ 正确原则


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

1appScope.launch {
2    TaskRunner.test()
3}
4

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

1withContext(Dispatchers.IO) {
2    File.read()
3}
4

✔ 原则 3:避免 IO 套 IO

1 IO  IO  Executor
2

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

1suspend fun test() =
2    withContext(executor) { }
3

9. 结论

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

源码:ScopeSample

这个是源码里的场景

这是去除在viewModel 里去除

1launch(Dispatchers.IO)
2

和 Application 里换成

1private val applicationScope = CoroutineScope(SupervisorJob() + appExecutor.asCoroutineDispatcher())
2

的场景


协程深度解析:明明只有 8 个异步任务,为何 App 线程数瞬间突破 一倍?》 是转载文章,点击查看原文


相关推荐


码农的AI翻身(三)你好,我叫 Embedding
Kfaino2026/6/28

AI翻身(三) 你好,我叫 Embedding——AI终于学会了理解,而不是死记硬背 大家好。 我叫 Embedding。 有人叫我: 向量。 有人叫我: 词向量。 还有人喜欢给我起一个特别高大上的名字: 语义空间映射。 听起来很厉害。 其实。 我就是一个翻译。 不过。 我翻译的不是中文和英文。 我翻译的是: 文字和数学。 我第一次见到老板的时候 老板(Transformer)对我说: "以后,人类说什么,你负责翻译。" 我愣住了。 "我不会中文啊。" 老板笑了。 "没关系。" "我也不会


笑抽了!DeepSeek识图,豆包完胜了!
甲维斯2026/6/19

听说 DeepSeek 识图功能上线了,我非常兴奋啊!终于要补上多模态这个短板了么? 打开APP和官网看了一眼: 真的出现了一个识图模式!哇塞! 我赶紧拿“梁爷爷”的图片试一波! 看到结果那一刻我瞬间,我忍不住笑出了声! 我的世界观被颠覆了。 原来这个人是腾讯公司高级副总裁、微信创始人张小龙。 我继续追问:那这个人是谁呢? 哇,世界观再次被颠覆!原来这两个人是同一个人?只是换了一个休息的造型而已??? 牛逼,还说出了 1、2、3、4,有理有据!好的,我信你了,这个人叫“张小龙”! 但是


Claude Code 每次调用 API 时,上下文是怎么"拼"出来的?
candyTong2026/6/11

Claude Code 每次调用模型 API 时,传给 API 的 payload 由三部分组成: System Prompt — 定义 Agent 的身份、行为规范和会话上下文 Tools — 工具 schema 列表,告诉模型有哪些能力可用 Messages — 对话消息,包含用户指令、CLAUDE.md 配置、工具执行结果 这三部分都会在 Agent Loop 调用模型时传入,但它们的来源不同:System Prompt 和 Tools 主要在进入循环前准备好,并在循环中保持相对稳定;


AI 降低了『写代码』的门槛,但是没有降低『软件开发』的复杂度
勇哥Java实战2026/6/4

好久没写文章了。 心里想写,却总觉得缺点由头。直到最近经历了几件事,彻底颠覆并重塑了我对 AI 编程的认知,骨鲠在喉,不吐不快。 我想聊聊那个被很多人忽略的真相:AI 确实拉低了「写代码」的门槛,但它并没有降低「软件开发」的复杂度。 1 售前朋友给我的惊喜 我曾经开源过一个项目——platform-sms。这是一个基于 SpringBoot 开发的短信网关服务,提供客户端 SDK,支持阿里云、腾讯云、亿美、合一等主流短信渠道,非常适合中小型公司。 这个项目最核心的设计亮点,在于我参考了阿里知名


CCFast 驰骋低代码BPM-积木菜单设计思想
驰骋低代码、工作流、表单引擎2026/5/28

CCFast 驰骋低代码 BPM:积木菜单设计思想  一、概述:为什么从“菜单”出发做低代码 1. CCFast 驰骋低代码 BPM 是一款开源的低代码开发与流程平台,面向企业信息化与业务流程数字化场景。 2. 本文章阐述:驰骋低代码 BPM 的整体体验与交付结构,根植于一套清晰、可扩展的菜单体系。 3. 其核心理念可以概括为:以菜单体系为骨架的低代码开发与运行平台——不是零散堆页面,而是用“可被授权、可被复用、可被组合”的菜单单元搭建系统。 4. 底座能力运行在组织结构管理与系统权限


RAG 系列(八):RAG 评估体系——用数据说话
冬奇Lab2026/5/6

为什么"感觉不错"不是标准? 前面七篇文章,我们搭起了一整套 RAG 流程:分块、Embedding、向量库、检索策略。系统跑起来了,你问它几个问题,回答看起来"还不错"。 但问题接踵而至: 迭代后真的变好了吗? 你换了 Embedding 模型、调了 chunk_size、加了 MMR,但回答质量真的提升了吗?还是只是"感觉"变好了? 问题出在哪里? 某个问题回答得很差,是检索阶段没召回相关文档,还是生成阶段模型在胡说八道? 怎么向老板汇报? "我觉得我们的 RAG 系统挺好的"——这句话在


告别重复劳动:一套插件让 AI 替你写代码、修Bug、做测试、上生产
吴文周2026/4/26

Claude Code 团队 AI 插件实践:从新人上线到全栈自动化的渐进式指南 特别鸣谢:本文由 南京大翼航空 团队实践沉淀而成,感谢团队在 AI 辅助研发领域的持续探索与投入。 后续规划:本文为 dw 插件生态的总览。后续将为每个 skill 单独撰写详细教程文章,涵盖实战案例、配置细节和踩坑经验,敬请关注。 本文涉及的研发规范体系均基于 Claude Code 的插件机制实现。插件是 Claude Code 官方提供的扩展方式,支持自定义命令、Skill、Hook、Agent 等,是


开发RN项目时,如何调试iOS真机、Android真机?常见调试问题排查?
光影少年2026/4/17

在开发 React Native(RN)项目时,真机调试是必备技能。下面我从 iOS / Android 真机调试步骤 + 常见问题排查 给你一套实战指南(偏工程经验总结)。 一、iOS 真机调试 1. 基本前提 必须使用 Xcode 需要 Apple ID(免费也行) iPhone 用数据线连接 Mac 2. 配置步骤 ✅ 第一步:信任设备 iPhone 上点击“信任此电脑” ✅ 第二步:Xcode 配置签名 打开: ios/xxx.xcwo


Spark DynamicJoinSelection 规则根据AQE统计信息动态调整Join策略
鸿乃江边鸟2026/4/9

背景 本文基于Spark 3.5.3 在Spark引入了AQE以后,Spark在运行的时候能够拿到运行时候的Shuffle统计信息,这些信息可以更好的来调整join的策略,当下规则下这种策略的调整是通过增加hint来进行控制的, 规则的目的是防止负优化。 分析 这里会有三种优化场景: 1. 检测大量空分区 → 添加 NO_BROADCAST_HASH HINT 对应的代码如下: private def hasManyEmptyPartitions(mapStats: MapOutputStat


金融和电商行业如何使用网络监控保障业务稳定?
运维行者_2026/4/1

在金融和电商的世界里,系统"稳定"从来不是一句口号------它是真金白银的保障。一笔支付交易卡顿3秒,可能让客户放弃下单;一次核心数据库响应延迟,就可能触发监管警报;一张快递面单打印失败,背后是成百上千个订单履约受阻。这些看似微小的技术波动,在高并发、强实时的业务场景中会被迅速放大,直接侵蚀用户体验、品牌信誉甚至合规底线。 OpManager 深知,对金融与电商企业而言,网络监控的意义远不止"看设备是否在线"。它必须能穿透复杂的IT架构,预判风险、快速定位、自动响应,并在关键时刻成为业务连续

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 聚合阅读