Suspend function — 是带有 suspend 修饰符的函数,可以在不阻塞线程的情况下暂停执行,并在稍后在同一个协程中恢复。根据 JetBrains Kotlin Docs, 2025,suspend 函数是协程的基本构建块,无需回调即可实现异步。每个 suspend 函数都被编译为基于 Continuation 的状态机,从而有效管理暂停点。
要点
Suspend function — 是使用 suspend 关键字声明的函数,可以在一个或多个点暂停执行而不阻塞线程。在另一个 suspend 函数内部每次调用 suspend 函数都是一个潜在的暂停点。
suspend fun fetchUserData(): User {
val response = httpClient.get("/user")
return parser.parse(response)
}
Kotlin 编译器将这样的函数转换为状态机。每个暂停点(调用另一个 suspend 函数)成为一个状态(label)。当前线程在状态之间被释放,等待的操作完成后,执行从下一个状态继续。
Suspend 函数出现在 Kotlin 1.3(2018 年)中,与协程一起作为实验性功能,并在 Kotlin 1.5(2021 年)中稳定。在此之前,Kotlin/Java 中的异步通过回调、RxJava 和 CompletableFuture 实现。Suspend 函数提供了线性语法和自动线程管理的替代方案。
理解 suspend 函数的内部结构是正确使用协程的关键。与普通函数不同,每个 suspend 函数都被编译为具有 Continuation 接口的类。
Kotlin 编译器在每个 suspend 函数的参数末尾添加一个 Continuation 类型的参数。Continuation 包含:
假设我们有一个 suspend 函数,包含对其他两个 suspend 函数的调用:
suspend fun process() {
val a = stepOne()
val b = stepTwo(a)
println(b)
}
编译器将其转换为带有标签的状态机:
// 生成的代码的简化表示
fun process(cont: Continuation<Unit>): Any? {
val cont = cont as ProcessContinuation
when (cont.label) {
0 -> {
cont.label = 1
if (stepOne(cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
1 -> {
cont.label = 2
val a = cont.result as TypeA
if (stepTwo(a, cont) == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
}
2 -> {
println(cont.result)
Unit
}
}
}
关键观察:如果函数返回 COROUTINE_SUSPENDED,当前线程被释放。当异步操作完成时,调用 Continuation.resumeWith,状态机从下一个标签继续执行。
Suspend 函数的声明与普通函数没有区别,只是 fun 前面加了 suspend 关键字。只有一个限制:suspend 函数只能从协程或其他 suspend 函数中调用。
suspend fun delayAndReturn(ms: Long): String {
delay(ms)
return "Done after ${ms}ms"
}
在这个示例中,delay 也是一个 suspend 函数,它在不阻塞线程的情况下将协程暂停指定毫秒数。延迟后,执行恢复。
fun main() = runBlocking {
val result = delayAndReturn(1000)
println(result)
}
runBlocking 在普通世界和协程之间创建了一座桥梁。在 lambda 内部,允许调用任何 suspend 函数。
Kotlin 支持函数类型的 suspend 版本——suspend () -> T 和 suspend (A) -> B。这允许将异步 lambda 传递给高阶函数:
suspend fun withRetry (
retries: Int = 3,
block: suspend () -> T
): T {
repeat(retries - 1) {
try { return block() }
catch (_: Exception) { delay(100) }
}
return block()
}
withRetry 函数接受一个 suspend lambda,并在出错时重复执行。这是带有重试的网络请求的典型模式。
Suspend 函数和普通函数之间的区别不仅仅是简单地添加修饰符。让我们看看根本的区别。
| 特性 | 普通函数 | Suspend 函数 |
|---|---|---|
| 执行线程 | 阻塞线程直到完成 | 可以释放线程并在稍后恢复 |
| 编译器参数 | 仅指定参数 | 末尾隐式 Continuation |
| 从普通函数调用 | 是 | 否 |
| 栈 | 线程的物理栈 | 堆上的状态机 + 点之间的物理栈 |
| 返回值 | 直接值 | 值或 COROUTINE_SUSPENDED |
| 性能 | 最小开销 | ~每次状态机几纳秒 (Kotlin 1.9+) |
普通函数没有 Continuation——没有地方保存状态和恢复执行。如果需要在普通函数中调用 suspend 函数,请使用 runBlocking(用于测试)或 CoroutineScope.launch(用于生产环境,考虑生命周期)。
让我们看看在 Kotlin 的 Android 应用中使用 suspend 函数的三个真实场景。
Room 直接支持 suspend 函数——查询自动在后台执行:
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE id = :id")
suspend fun getUser(id: Int): User?
@Insert
suspend fun insertUser(user: User)
}
Room 内部使用 Dispatchers.IO 执行查询,结果返回到调用 suspend 函数的调度器。
class ProfileViewModel : ViewModel() {
private val repo = ProfileRepository()
fun loadProfile(id: String) {
viewModelScope.launch {
val profile = repo.getProfile(id)
_profile.value = profile
}
}
}
ViewModelScope.launch 创建一个协程,在其中调用 getProfile suspend 函数。获取结果后,UI 在主线程上更新。
suspend fun placeOrder(cart: Cart): OrderResult {
val validated = validateCart(cart)
val payment = processPayment(validated)
val receipt = sendReceipt(payment)
return receipt
}
三个 suspend 函数顺序执行。在每一步,协程可以暂停而不阻塞线程。如果某一步抛出异常——其他步骤不会执行,从而保护订单状态的正确性。
即使是有经验的 Kotlin 开发人员在设计 suspend 函数时也会犯错。让我们看看最常见的错误。
Suspend 函数不会自动使代码异步。Thread.sleep()、InputStream.read() 和其他阻塞调用仍然会阻塞线程。使用 withContext(Dispatchers.IO) 包装阻塞操作。
如果函数不调用其他 suspend 函数且不执行异步操作——suspend 修饰符是多余的。它增加了状态机的开销并限制了调用上下文。只有在函数确实会暂停时才将其设为 suspend。
取消协程时,suspend 函数会抛出 CancellationException。不要盲目捕获它——您将剥夺调用代码正确完成取消的能力。如果需要执行终结操作,请使用 finally 块和 NonCancellable。
suspend fun safeOperation() {
try {
doWork()
} finally {
withContext(NonCancellable) {
cleanup()
}
}
}
finally 块始终执行,包括取消,而 NonCancellable 保证清理不会被中断。
无法在未创建协程的情况下直接从回调调用 suspend 函数。使用 suspendCoroutine 或 suspendCancellableCoroutine 将回调风格适配到协程。
常见问题
可以,技术上 suspend 函数可以不调用其他 suspend 函数。编译器会创建一个具有一个状态(label 0)的状态机。然而,这样的函数实际上没有用处——它像普通函数一样执行,但有额外开销。不要不必要地使用 suspend。
Kotlin 提供了 kotlinx-coroutines-debug 库——带有 DebugProbes 和协程跟踪工具。在 Android Studio Arctic Fox 及更高版本中,Debugger 中有一个内置的 Coroutines 选项卡,显示活动协程、它们的状态和暂停点。
每个暂停点都会在状态机中创建一个新状态。对于大多数应用程序,一个点的开销是纳秒级的(Kotlin 1.9+)。只有在循环中有数万个点时,才值得合并操作或使用 sequence/flow。
在 Kotlin 中,suspend 是函数类型的修饰符,而不是返回值的标记(如 C# 中的 async)。任何 suspend 函数都可以有任何参数和返回类型,其调用在语法上与普通函数调用没有区别——调用点没有 await 运算符。
使用 suspendCancellableCoroutine 进行适配。在其中,传递调用 continuation.resume() 的回调注册,如果回调支持取消订阅,则返回取消令牌。这是包装旧 Android API 的标准模式。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。