Android 面试系列:Kotlin 协程的 delay 到底发生在哪个线程?

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

在开发中,我们经常写出这样的代码:

1mainScope.launch {
2    log("start")
3    delay(1000)
4    log("end")
5}
6
7

这段代码看起来非常简单,但它隐藏了一个非常经典的问题:

delay 这 1 秒到底发生在哪个线程? 主线程前后都在执行,那中间谁在“等时间”?


结论

delay 从来不占用线程等待,它是一次“挂起 + 时间注册 + 调度恢复 + 状态机推进”的过程。

中间没有任何业务线程在 sleep,也没有线程在阻塞计时。

可以先运行我的 Demo:

👉 Delay 实现原理(自制 EventLoop) (非 Android 环境)


🧩 一、delay 的四阶段模型

为了彻底理解 delay,我们将其拆解为底层运作的四个连续阶段:


🧩 第一阶段:Suspend(挂起)

当代码执行到 delay(1000) 时,协程会立即触发挂起:

1. 保存 Continuation

后续的业务代码(如 log("end"))会被作为挂起点的剩余逻辑,封装成一个 Continuation(即状态机对象)。

2. 线程立即释放

当前线程(如 UI 主线程)发生的是:

  • ❌ 没有 Thread.sleep()
  • ❌ 没有死循环阻塞
  • ❌ 没有 CPU 空转

当前协程直接 “保存状态并释放当前线程的执行权”。如果是 Android 主线程,它会立刻回到底层的消息循环去处理其他 UI 绘制或点击事件,绝对不会卡顿。


🧩 第二阶段:Schedule(时间注册)

既然线程释放了,总得有人负责“记住这 1 秒”。这一阶段,协程会将刚刚封装好的恢复任务注册到对应环境的时间队列中

🌟 情况 1:Android 环境(Dispatchers.Main)

协程最终会把任务交给 Android 的底层消息机制:

1handler.sendMessageDelayed(msg, 1000)
2
3
  • 谁在等? 并不是协程在等,而是 Android 系统的 MessageQueue 配合底层的 nativePollOnce() (epoll 机制) 在管理时间。

🌟 情况 2:纯 JVM / 非 Android 环境

如果没有 Android Looper,delay 会 Fallback 降级到协程全局的:

  • DefaultExecutor:一个全局的延迟守护线程。它通过 LockSupport.parkNanos() 配合内部的延迟任务队列(时间轮或最小堆结构)来统一管理延迟。

本质:无论是哪种环境,都是将任务托管给了底层的调度系统/守护线程推进时间。


🧩 第三阶段:Dispatch(调度恢复)

当 1000ms 时间到了之后,底层的计时器(LooperDefaultExecutor)会触发回调。但注意:此时并不是直接盲目地去跑 log("end")

任务会先经过:

1Dispatcher.dispatch()
2
3
  • 为什么要这一步? 因为计时器触发时所在的线程,未必是协程原本要求的线程(例如 DefaultExecutor 是一个独立的守护线程)。
  • 核心职责Dispatcher 负责重新检查协程的上下文,并决定“在哪个线程去恢复这个协程”。它会将恢复任务重新 post(投递)回目标线程的任务队列中(例如重新投递回 Android 的主线程队列)。

🧩 第四阶段:Resume(恢复状态机)

目标线程消费到这个调度任务后,协程框架重新接管控制权。

1. 恢复 Continuation

调用 continuation.resume(Unit),重新唤醒先前保存的协程状态机。

2. 执行剩余代码

协程从挂起点向下推进,最终执行:

1log("end")
2
3

整个生命周期至此完美闭环。


二、用一个自制 EventLoop 看清 delay 本质

为了彻底验证并看清上述四个阶段,我写了这套 Demo 来完美还原一个“协程 delay 调度系统”。

1. Looper(事件循环)

1Looper.prepare()
2Looper.loop()
3
4
  • 本质:一个 while(true) 的消息循环,用于模拟线程不阻塞时的任务消费。

2. Handler(任务投递)

1fun sendMessageDelayed(msg: Message, delayMillis: Long)
2
3
  • 本质:负责把任务放入队列,并指定未来的执行时间。

3. Message(任务载体)

1class Message {
2    var whenTime: Long = 0L
3    var callback: Runnable? = null
4    var target: Handler? = null
5}
6
7
  • 本质:一个“未来要执行的动作包装”。

4. CoroutineDispatcher(协程桥接)

1class HandlerDispatcher(
2    private val handler: Handler
3) : CoroutineDispatcher(), Delay
4
5

三、delay 的真实实现(核心拦截)

在我的自制调度器中,delay 的核心对接点在于实现 scheduleResumeAfterDelay 接口:

1override fun scheduleResumeAfterDelay(
2    timeMillis: Long,
3    continuation: CancellableContinuation<Unit>
4) {
5    // 【对应 Dispatch & Resume】把“恢复协程”封装成 Runnable 任务
6    val block = Runnable {
7        with(continuation) {
8            // 官方的标准实现会经过 dispatcher.dispatch 重新分发线程
9            // 这里在 Looper 线程直接恢复
10            resumeUndispatched(Unit) 
11        }
12    }
13
14    // 【对应 Schedule】封装成 Message,丢进延迟队列
15    val msg = Message().apply {
16        callback = block
17    }
18    handler.sendMessageDelayed(msg, timeMillis)
19}
20
21

🔥 四、完整 delay 执行链路图

1launch (启动协程)
2   
3delay(1000)
4   
5【1. Suspend】挂起协程(保存 Continuation 状态,释放当前线程)
6   
7【2. Schedule】调用 scheduleResumeAfterDelay  封装 Message(callback = resume) 投入队列
8   
9【底层的等待】MessageQueue / DefaultExecutor 在不阻塞业务线程的前提下推进时间
10   
11【3. Dispatch】时间到!Looper 捞出消息,通过 Dispatcher 分发,决定在哪个线程恢复
12   
13【4. Resume】调用 continuation.resume() 恢复状态机
14   
15继续执行 "end"
16
17

五、结论

  • **delay 的本质是:状态机挂起(Suspend) →\rightarrow→ 时间注册(Schedule) →\rightarrow→ 线程分发(Dispatch) →\rightarrow→ 回调恢复(Resume)
  • 整个挂起期间,没有线程 sleep、没有线程阻塞、没有 CPU 空转,有的只是调度系统在极其高效地推进时间。

🚀 六、这套demo的价值

这套代码虽然是一个 Demo,但它它用最精简的代码表达了:EventLoopMessageQueueDispatcherDelay 以及 Continuation 之间的闭环协作。

👉 欢迎去我的 GitHub 仓库阅读完整源码实现


Android 面试系列:Kotlin 协程的 delay 到底发生在哪个线程?》 是转载文章,点击查看原文


相关推荐


别再只会用 cron:Linux systemd Timer 定时任务实战详解
唐青枫2026/6/30

简介 Linux 上提到定时任务,最先想到的通常是 cron。 cron 足够简单,也足够稳定,但任务一旦涉及日志、启动依赖、超时控制、错过后补跑、运行用户和资源限制,单独一行 crontab 很快就会变得难以维护。 systemd Timer 提供了另一套方案: .timer 负责决定什么时候执行 .service 负责决定执行什么、以什么方式执行 例如,每天凌晨备份一次应用数据,可以拆成两个单元: myapp-backup.timer | | 到达触发时间


火山 DTS 正式支持 MySQL 同步到 Milvus , 解决业务库到向量库最后一公里
火山引擎Agent社区2026/6/21

这两年,大模型、智能问答越来越多地落到实际业务里。很多企业在推进过程中慢慢发现,影响 AI 应用落地效率的,除了模型本身能力之外,数据链路是否能顺畅跑通,也同样非常关键。 目前,企业大部分的业务数据库依然在关系型数据库中,而AI应用对支撑语义检索、相似召回的向量数据库有着更强的依赖。怎么把结构化业务数据稳定、持续地同步到向量数据库,正在成为不少企业建设 AI 数据底座时绕不开的问题。 现在,火山引擎 DTS 正式支持 MySQL 同步到 Milvus,帮助企业快速打通从业务数据库到向量数据库的数


计算机网络基础:在 P2P 对等方中搜索对象
梁辰兴2026/6/13

📌目录 ⚖️ 在P2P对等方中搜索对象:去中心化网络的信息发现机制🎯 一、P2P搜索问题概述:去中心化带来的挑战(一)搜索问题的本质(二)搜索算法设计目标(三)搜索算法的分类体系 📦 二、无结构P2P网络中的搜索机制(一)泛洪查询机制(二)随机漫步搜索(三)迭代加深搜索(四)Gossip协议搜索(五)向量时钟与语义搜索 🌐 三、分布式哈希表:结构化搜索的突破(一)DHT的基本原理(二)Chord算法详解(三)CAN算法详解(四)Kademlia算法详解(五)Pastry与T


真正值钱的 AI 小工具,可能只是帮人少打一遍字
深海恶霸Grace2026/6/6

我有个朋友是做财务兼采购的。 他最近有个特别烦的工作: 整理各种报价单信息。 有时候是供应商发来的截图。 有时候是一张图片。 有时候干脆就是一段文字描述。 最后这些东西都要被他重新整理进 Excel。 项目名称、规格、数量、单位、单价、总价。 听起来不难。 但真正做起来,很折磨。 因为这不是一道复杂题。 这是重复劳动。 你得盯着图片看一眼,再切到 Excel 里打一条。 再回来看一眼,再打一条。 遇到数字多一点、截图糊一点、格式乱一点的时候,眼睛真的会看花。 最烦的是,录完之后还不能放心。 因为


Sqoop 安装完整教程(基于 WSL2 + Ubuntu 24.04)
穆金秋2026/5/30

本教程详细介绍了在WSL2+Ubuntu24.04环境下安装配置Sqoop1.4.7的完整流程: 环境准备 Java8+、Hadoop3.3.6、MySQL8.0.45已安装验证命令:java -version/hadoop version/mysql --version 安装步骤 下载Sqoop1.4.7并解压到/usr/local配置环境变量(SQOOP_HOME和PATH)安装MySQL JDBC驱动到Sqoop/lib目录解决依赖问题(commons-lang等jar包)


Gogs: 打造属于你自己的轻量级 Git 服务
修己xj2026/5/8

在软件开发的世界里,Git 已经成为版本控制的事实标准。GitHub、GitLab 等平台提供了强大的托管服务,但有时候,我们需要一个完全属于自己的私有 Git 仓库——可能是为了代码安全,可能是为了定制化需求,可能是为了集成到现有服务中,也可能只是想在自己的服务器上搭建一个个人代码库。开源gitlab有点重,最近我在GitHub上发现了一个轻量级项目Gogs。 什么是 Gogs? Gogs 是一个用 Go 语言编写的自助 Git 托管服务。它的目标是以最简单、最轻松的方式搭建一个简单、稳定且


【系统架构师案例题-知识点】数据库与缓存设计
roman_日积跬步-终至千里2026/4/28

本文聚焦系统架构师案例题中的数据库与缓存设计,重点说明关系型数据库设计、NoSQL 选型、分库分表、读写分离、缓存策略、缓存故障模式以及缓存与数据库一致性问题,并结合电商、支付、内容平台、搜索系统、推荐系统等真实软件行业场景说明这些技术为什么会出现、各自解决什么问题、工程上该如何取舍。 阅读时可以按三个层次把握:先理解数据为什么会成为瓶颈,再理解数据库和缓存分别解决哪一类问题,最后把题干中的业务信号翻译成卷面表达。 一、先建立整体认识 数据库与缓存设计的核心,不是“会不会背名词”,而是看清系统到


Visual Studio 与 Visual Studio Code 区别
日更嵌入式的打工靓仔2026/4/20

特性Visual Studio (VS)Visual Studio Code (VS Code)本质类型集成开发环境 (IDE)轻量级源代码编辑器核心定位大型、复杂的项目开发(Windows、游戏、企业级应用)快速编辑、脚本编写、Web/云开发主要平台Windows、macOS (功能有差异)Windows、macOS、Linux占用空间大 (安装需要几GB到几十GB空间)小 (安装包约100MB以下)性能/速度启动和加载大型项目较慢启动迅速,打开文件极快价格社区版免费;专业版/企业版付费完全免


OpenClaw Windows 安装详细教程
超低空2026/4/11

OpenClaw(前身为 ClawdBot)是一款本地托管的个人 AI 助手系统,可以通过网关控制平面连接到 WhatsApp、Telegram、Discord 等常见通讯软件,并在本地运行各种工作流。 由于 OpenClaw 深度依赖底层系统的进程管理和文件监听,直接在 Windows 原生环境下运行可能会遇到一些限制。因此,官方推荐使用 WSL2(Windows Subsystem for Linux) 或 Docker 来进行安装。以下是详细的安装教程和避坑指南。 安装方式优缺点对比 在


别让APP名字和图标毁了你的Toast!一招教你Android优化技巧
小码哥_常2026/4/3

别让APP名字和图标毁了你的Toast!一招教你Android优化技巧 为啥要去掉 Toast 里的 APP 名字和图标 在如今这个看脸的时代,APP 的颜值也至关重要。统一、美观的 UI 设计,就像给 APP 穿上了一件漂亮的外衣,不仅能提升用户体验,还能让 APP 在众多竞争对手中脱颖而出。 大家在使用 APP 的时候,应该都遇到过 Toast 消息提示吧。这是一种轻量级的消息提示框,通常出现在屏幕底部,用来告知用户一些操作结果或者系统状态。但是,不知道大家有没有注意到,在某些手机上,比如小

首页编辑器站点地图

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

Copyright © 2026 聚合阅读