Kotlin中suspend的学习记录
什么是 suspend
● 核心概念: suspend 是 Kotlin 协程里标记“可挂起”的函数修饰符,表示该函数可以在不阻塞线程的情况下暂停与恢复执行。
● 本质: 不是新线程;只是让函数在挂起点让出线程,等结果回来再从挂起点继续。
如何声明与调用
suspend fun fetchUser(): User {
// 可能会挂起的操作,例如网络/IO
return api.getUser()
}
● 只能在以下地方调用 suspend 函数:
① 另一个 suspend 函数内部
② 协程构建器内:launch { … }、async { … }、runBlocking { … } 等
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
scope.launch {
val user = fetchUser() // 合法:在协程内
show(user)
}
指定执行线程/调度器
● CPU 密集 → Dispatchers.Default
● IO 密集 → Dispatchers.IO
● UI 更新 → Dispatchers.Main
suspend fun load(): Data = withContext(Dispatchers.IO) {
dao.query() // IO 线程执行
}
与阻塞的区别
● 挂起: 不阻塞线程,资源友好,适合大量并发
● 阻塞: Thread.sleep()、Future.get() 会占用线程,协程里尽量避免
delay(1000) // 挂起1秒,不阻塞线程
Thread.sleep(1000) // 阻塞线程1秒
并发与结构化并发
suspend fun parallel(): Pair<A, B> = coroutineScope {
val a = async(Dispatchers.IO) { loadA() }
val b = async(Dispatchers.IO) { loadB() }
a.await() to b.await()
}
● 使用 coroutineScope 限定生命周期,出错会取消子协程(结构化并发)。
异常处理与取消
val handler = CoroutineExceptionHandler { _, e -> log(e) }
scope.launch(handler) {
try {
doWork()
} catch (e: CancellationException) {
throw e // 取消异常要继续抛出,保持协程可被取消
} catch (e: Exception) {
// 业务异常处理
}
}
● 协程可取消;挂起点(如 delay、withContext、suspendCancellableCoroutine)会检查取消。
在非挂起 API 中桥接挂起
suspend fun awaitCallback(): Result = suspendCancellableCoroutine { cont ->
call.enqueue(object : Callback {
override fun onSuccess(r: Result) {
if (cont.isActive) cont.resume(r)
}
override fun onError(t: Throwable) {
if (cont.isActive) cont.resumeWithException(t)
}
})
cont.invokeOnCancellation { call.cancel() } // 取消协同
}
常见误区
● 在 suspend 里做重 CPU 任务却不切换到 Dispatchers.Default
● 在 suspend 里调用阻塞 API(如数据库/网络的阻塞实现)却不 withContext(Dispatchers.IO)
● 捕获并吞掉 CancellationException
● 滥用 GlobalScope,导致泄漏
● 用 runBlocking 卡住主线程(仅测试或入口场景使用)
launch、async和runBlocking的区别
● launch: 启动一个协程执行任务,不关心返回值。返回 Job。
● async: 启动一个协程并产生结果,需要 await() 获取。返回 Deferred。
● runBlocking: 在当前线程阻塞地运行协程,直到其内部执行完毕(一般只用于测试或main函数演示,Android主线程避免使用)。
| 构建器 | 用途 | 返回值 | 异常处理 |
|---|---|---|---|
| launch | 启动任务,不关心结果 | Job | 立即传播 |
| async | 启动任务,需要结果 | Deferred | 延迟传播 |
| runBlocking | 测试/桥接阻塞代码 | T | 立即传播 |
- launch
● 用途: 火并执行任务、更新UI、发送事件等“只做事不取值”的场景。
● 返回值: Job(可取消、可 join() 等待完成)。
● 异常: 未捕获异常会向上抛到其作用域的异常处理器(如 CoroutineExceptionHandler)。
val job = scope.launch(Dispatchers.IO) {
doWork() // 无返回值
}
// 需要时可取消或等待
job.cancel()
job.join()
- async
● 用途: 并发执行并“需要结果”的子任务,配合 await()。
● 返回值: Deferred(是 Job 的子类型)。
● 异常: 会在 await() 时抛出(延迟传播),可在 await() 处 try/catch
val r1 = scope.async(Dispatchers.IO) { loadA() }
val r2 = scope.async(Dispatchers.IO) { loadB() }
val result = r1.await() + r2.await()
- runBlocking
● 用途: 把协程世界“桥接”到阻塞世界;常用于单元测试、fun main() 示例。
● 行为: 阻塞当前线程直到其代码块结束;Android 主线程请勿使用(会卡UI)。
fun main() = runBlocking {
val value = async { compute() }.await()
println(value)
}
coroutineScope和supervisorScope的区别
| 构建器 | 用途 | 返回值 | 异常处理 | 适用场景 |
|---|---|---|---|---|
| coroutineScope | 结构化并发 | T | 立即 | 需要所有子任务成功 |
| supervisorScope | 监督并发 | T | 隔离 | 部分失败可接受 |
- coroutineScope
● 用途: 创建新的协程作用域,用于结构化并发
● 返回值: T
● 异常处理: 立即传播异常,任何子协程失败都会取消整个作用域
suspend fun loadData(): Data = coroutineScope {
val user = async { loadUser() }
val config = async { loadConfig() }
// 如果任何一个子协程失败,整个作用域都会被取消
Data(user.await(), config.await())
}
特点:
● 等待所有子协程完成
● 任何子协程失败都会取消其他子协程
● 适合需要所有子任务都成功的场景
- supervisorScope
● 用途: 创建监督作用域,子协程失败不影响其他子协程
● 返回值: T
● 异常处理: 子协程异常不会影响其他子协程
suspend fun loadData(): Data = supervisorScope {
val user = async { loadUser() }
val config = async { loadConfig() }
// 即使 loadUser() 失败,loadConfig() 仍会继续执行
try {
Data(user.await(), config.await())
} catch (e: Exception) {
// 处理部分失败的情况
Data(null, config.await())
}
}
特点:
● 子协程失败不会影响其他子协程
● 适合部分失败可接受的场景
更多推荐


所有评论(0)