suspend function:是什么、语法及其在协程中的工作方式

作者: IT Sectr 发布日期: 2026-06-22 阅读时间: 9 分钟

Suspend function — 是带有 suspend 修饰符的函数,可以在不阻塞线程的情况下暂停执行,并在稍后在同一个协程中恢复。根据 JetBrains Kotlin Docs, 2025,suspend 函数是协程的基本构建块,无需回调即可实现异步。每个 suspend 函数都被编译为基于 Continuation 的状态机,从而有效管理暂停点。

要点

  • Suspend — Kotlin 关键字,将函数标记为可暂停(异步)
  • Continuation — 编译器为每个 suspend 函数添加的隐藏参数,用于保存状态
  • 暂停点 — 调用其他 suspend 函数的位置,协程可以在不阻塞的情况下停止
  • 状态机 — suspend 函数的内部表示,每个暂停点是一个独立的状态
  • 仅从协程调用 — suspend 函数只能从另一个 suspend 函数或 launch/async 中调用

Kotlin 中的 suspend function 是什么?

Suspend function — 是使用 suspend 关键字声明的函数,可以在一个或多个点暂停执行而不阻塞线程。在另一个 suspend 函数内部每次调用 suspend 函数都是一个潜在的暂停点。

kotlin
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 函数如何工作:Continuation 和状态机

理解 suspend 函数的内部结构是正确使用协程的关键。与普通函数不同,每个 suspend 函数都被编译为具有 Continuation 接口的类

Continuation — 隐藏参数

Kotlin 编译器在每个 suspend 函数的参数末尾添加一个 Continuation 类型的参数。Continuation 包含:

  • context — CoroutineContext(调度器、job、上下文元素)
  • resumeWith — 用于以结果或异常恢复执行的方法
  • label — 状态机中当前状态的索引

状态机示例

假设我们有一个 suspend 函数,包含对其他两个 suspend 函数的调用:

kotlin
suspend fun process() {
    val a = stepOne()
    val b = stepTwo(a)
    println(b)
}

编译器将其转换为带有标签的状态机:

kotlin
// 生成的代码的简化表示
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 函数的语法:声明和调用

Suspend 函数的声明与普通函数没有区别,只是 fun 前面加了 suspend 关键字。只有一个限制:suspend 函数只能从协程或其他 suspend 函数中调用。

基本声明

kotlin
suspend fun delayAndReturn(ms: Long): String {
    delay(ms)
    return "Done after ${ms}ms"
}

在这个示例中,delay 也是一个 suspend 函数,它在不阻塞线程的情况下将协程暂停指定毫秒数。延迟后,执行恢复。

从协程调用

kotlin
fun main() = runBlocking {
    val result = delayAndReturn(1000)
    println(result)
}

runBlocking 在普通世界和协程之间创建了一座桥梁。在 lambda 内部,允许调用任何 suspend 函数。

Suspend lambda 和函数类型

Kotlin 支持函数类型的 suspend 版本——suspend () -> Tsuspend (A) -> B。这允许将异步 lambda 传递给高阶函数:

kotlin
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 函数和普通函数之间的区别不仅仅是简单地添加修饰符。让我们看看根本的区别。

特性普通函数Suspend 函数
执行线程阻塞线程直到完成可以释放线程并在稍后恢复
编译器参数仅指定参数末尾隐式 Continuation
从普通函数调用
线程的物理栈堆上的状态机 + 点之间的物理栈
返回值直接值值或 COROUTINE_SUSPENDED
性能最小开销~每次状态机几纳秒 (Kotlin 1.9+)

为什么不能从普通函数调用 suspend 函数

普通函数没有 Continuation——没有地方保存状态和恢复执行。如果需要在普通函数中调用 suspend 函数,请使用 runBlocking(用于测试)或 CoroutineScope.launch(用于生产环境,考虑生命周期)。

Android 中的 suspend 函数示例

让我们看看在 Kotlin 的 Android 应用中使用 suspend 函数的三个真实场景。

示例 1:带有 suspend 查询的 Room DAO

Room 直接支持 suspend 函数——查询自动在后台执行:

kotlin
@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 函数的调度器。

示例 2:组合 suspend 函数加载屏幕

kotlin
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 在主线程上更新。

示例 3:顺序异步步骤

kotlin
suspend fun placeOrder(cart: Cart): OrderResult {
    val validated = validateCart(cart)
    val payment = processPayment(validated)
    val receipt = sendReceipt(payment)
    return receipt
}

三个 suspend 函数顺序执行。在每一步,协程可以暂停而不阻塞线程。如果某一步抛出异常——其他步骤不会执行,从而保护订单状态的正确性。

使用 suspend 函数时的常见错误

即使是有经验的 Kotlin 开发人员在设计 suspend 函数时也会犯错。让我们看看最常见的错误。

错误 1:在 suspend 内部进行阻塞调用

Suspend 函数不会自动使代码异步。Thread.sleep()、InputStream.read() 和其他阻塞调用仍然会阻塞线程。使用 withContext(Dispatchers.IO) 包装阻塞操作。

错误 2:不必要地创建 suspend 函数

如果函数不调用其他 suspend 函数且不执行异步操作——suspend 修饰符是多余的。它增加了状态机的开销并限制了调用上下文。只有在函数确实会暂停时才将其设为 suspend。

错误 3:忽略 CancellationException

取消协程时,suspend 函数会抛出 CancellationException。不要盲目捕获它——您将剥夺调用代码正确完成取消的能力。如果需要执行终结操作,请使用 finally 块和 NonCancellable。

kotlin
suspend fun safeOperation() {
    try {
        doWork()
    } finally {
        withContext(NonCancellable) {
            cleanup()
        }
    }
}

finally 块始终执行,包括取消,而 NonCancellable 保证清理不会被中断。

错误 4:从回调中调用 suspend 函数

无法在未创建协程的情况下直接从回调调用 suspend 函数。使用 suspendCoroutinesuspendCancellableCoroutine 将回调风格适配到协程。

常见问题

Suspend 函数可以不包含暂停点吗?

可以,技术上 suspend 函数可以不调用其他 suspend 函数。编译器会创建一个具有一个状态(label 0)的状态机。然而,这样的函数实际上没有用处——它像普通函数一样执行,但有额外开销。不要不必要地使用 suspend。

如何调试 suspend 函数?

Kotlin 提供了 kotlinx-coroutines-debug 库——带有 DebugProbes 和协程跟踪工具。在 Android Studio Arctic Fox 及更高版本中,Debugger 中有一个内置的 Coroutines 选项卡,显示活动协程、它们的状态和暂停点。

暂停点的数量会影响性能吗?

每个暂停点都会在状态机中创建一个新状态。对于大多数应用程序,一个点的开销是纳秒级的(Kotlin 1.9+)。只有在循环中有数万个点时,才值得合并操作或使用 sequence/flow。

Suspend 函数与其他语言中的 async/await 有何不同?

在 Kotlin 中,suspend 是函数类型的修饰符,而不是返回值的标记(如 C# 中的 async)。任何 suspend 函数都可以有任何参数和返回类型,其调用在语法上与普通函数调用没有区别——调用点没有 await 运算符。

如何将回调函数转换为 suspend?

使用 suspendCancellableCoroutine 进行适配。在其中,传递调用 continuation.resume() 的回调注册,如果回调支持取消订阅,则返回取消令牌。这是包装旧 Android API 的标准模式。

总结

  • Suspend function — 带有 suspend 修饰符的函数,通过 Continuation 机制在不阻塞线程的情况下暂停执行
  • 状态机 — Kotlin 字节码中 suspend 函数的内部表示,每个暂停点是一个带有标签的独立状态
  • Continuation — 包含协程上下文和 resumeWith 方法以恢复执行的隐藏参数
  • 仅从协程调用 — 没有 runBlocking 或 CoroutineScope,suspend 函数无法从普通函数访问
  • 阻塞操作 在 suspend 内部需要 withContext(Dispatchers.IO) — 否则线程会被阻塞
  • Room 和 Retrofit 原生支持 suspend 函数,自动管理后台线程
  • CancellationException — 通过 finally + NonCancellable 处理取消,不要盲目捕获 CancellationException

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读