ZetCode

Kotlin suspend 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的协程提供了一种强大的方式来编写异步代码。suspend 关键字标记可以暂停执行而不阻塞线程的函数。本教程通过实际例子深入探讨了挂起函数。

基本定义

suspend 关键字将函数标记为挂起函数。此类函数可以暂停执行,稍后恢复,而不会阻塞线程。它们只能从协程或其他挂起函数中调用。挂起函数是 Kotlin 协程系统的核心。

基本挂起函数

一个简单的挂起函数演示了基本语法。该函数可以执行长时间运行的操作,而不会阻塞调用线程。它使用 Kotlin 协程库中的 delay 函数。

BasicSuspend.kt
package com.zetcode

import kotlinx.coroutines.*

suspend fun greet() {
    delay(1000)
    println("Hello from suspend function!")
}

fun main() = runBlocking {
    greet()
    println("Done")
}

此示例显示了一个简单的挂起函数 greetrunBlocking 协程构建器创建协程上下文。该函数使用 delay 暂停 1 秒,然后打印一条消息。

带参数的挂起函数

挂起函数可以像常规函数一样接受参数。它们也可以返回值。这使得它们对各种异步操作具有灵活性。该示例显示了一个处理输入的挂起函数。

SuspendWithParams.kt
package com.zetcode

import kotlinx.coroutines.*

suspend fun processData(data: String): String {
    delay(500)
    return "Processed: $data"
}

fun main() = runBlocking {
    val result = processData("Kotlin")
    println(result) // Output: Processed: Kotlin
}

processData 函数接受一个 String 参数,并在延迟后返回一个处理后的版本。runBlocking 协程构建器允许从常规代码调用此挂起函数。

顺序挂起调用

挂起函数可以按顺序调用其他挂起函数。执行在每个挂起点暂停。这创建了可读的异步代码,看起来像同步代码。

SequentialCalls.kt
package com.zetcode

import kotlinx.coroutines.*

suspend fun fetchUser(): String {
    delay(1000)
    return "User123"
}

suspend fun fetchPosts(user: String): List<String> {
    delay(1000)
    return listOf("Post1", "Post2")
}

fun main() = runBlocking {
    val user = fetchUser()
    val posts = fetchPosts(user)
    println("$user has posts: $posts")
}

此示例显示了按顺序调用的两个挂起函数。首先运行 fetchUser,然后 fetchPosts 使用其结果。总执行时间约为 2 秒,但代码仍然干净。

使用 async 进行并行执行

挂起函数可以使用 async 并行运行。这对于可以并发执行的独立操作很有用。await 函数暂停,直到结果准备就绪。

ParallelExecution.kt
package com.zetcode

import kotlinx.coroutines.*

suspend fun fetchProfile(): String {
    delay(800)
    return "Profile Data"
}

suspend fun fetchNotifications(): List<String> {
    delay(600)
    return listOf("Notification1", "Notification2")
}

fun main() = runBlocking {
    val profileDeferred = async { fetchProfile() }
    val notificationsDeferred = async { fetchNotifications() }
    
    val profile = profileDeferred.await()
    val notifications = notificationsDeferred.await()
    
    println("$profile with $notifications")
}

在这里,两个挂起函数使用 async 并行运行。总执行时间约为 800 毫秒(最长的操作),而不是 1400 毫秒。await 调用暂停,直到两个操作完成。

带有 try-catch 的挂起函数

挂起函数可以使用标准 Kotlin 错误处理。异常的工作方式与常规函数相同。这使得协程中的错误处理变得熟悉而直接。

SuspendTryCatch.kt
package com.zetcode

import kotlinx.coroutines.*

suspend fun riskyOperation(): String {
    delay(500)
    if (Math.random() > 0.5) {
        throw RuntimeException("Operation failed!")
    }
    return "Success"
}

fun main() = runBlocking {
    try {
        val result = riskyOperation()
        println(result)
    } catch (e: Exception) {
        println("Caught: ${e.message}")
    }
}

riskyOperation 函数可能会抛出异常。我们像使用常规函数一样,将调用包装在 try-catch 块中。异常处理在挂起函数中的工作方式相同。

挂起 Lambda 表达式

Lambda 表达式也可以标记为挂起。这允许编写可以传递的异步代码块。该示例显示了与 withContext 一起使用的挂起 lambda。

SuspendLambda.kt
package com.zetcode

import kotlinx.coroutines.*

suspend fun performOperation(block: suspend () -> Unit) {
    println("Operation starting")
    block()
    println("Operation complete")
}

fun main() = runBlocking {
    performOperation {
        delay(1000)
        println("Middle of operation")
    }
}

performOperation 函数接受一个挂起 lambda。lambda 可以包含挂起调用,例如 delay。这种模式在基于协程的库中很常见。

挂起函数中的协程上下文

挂起函数可以访问协程上下文。它们可以使用 coroutineContext 检查或修改它。这对于结构化并发和上下文传播很有用。

CoroutineContext.kt
package com.zetcode

import kotlinx.coroutines.*

suspend fun printContextInfo() {
    println("Running in ${coroutineContext[CoroutineName]?.name}")
    println("Dispatcher: ${coroutineContext[CoroutineDispatcher]}")
}

fun main() = runBlocking(CoroutineName("MainCoroutine")) {
    printContextInfo()
}

printContextInfo 函数访问协程上下文。它打印协程名称和调度器。上下文从父协程传递给挂起函数。

挂起函数的最佳实践

来源

Kotlin 协程文档

本教程深入介绍了 Kotlin 的 suspend 关键字,展示了如何编写和使用挂起函数。我们探讨了各种场景,包括顺序和并行执行、错误处理和上下文感知。正确使用挂起函数可以使异步代码更干净、更易于维护。

作者

我叫 Jan Bodnar,是一位热情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我写了超过 1400 篇文章和 8 本电子书。我有超过八年的编程教学经验。

列出 所有 Kotlin 教程