Kotlin suspend 关键字
最后修改于 2025 年 4 月 19 日
Kotlin 的协程提供了一种强大的方式来编写异步代码。suspend 关键字标记可以暂停执行而不阻塞线程的函数。本教程通过实际例子深入探讨了挂起函数。
基本定义
suspend 关键字将函数标记为挂起函数。此类函数可以暂停执行,稍后恢复,而不会阻塞线程。它们只能从协程或其他挂起函数中调用。挂起函数是 Kotlin 协程系统的核心。
基本挂起函数
一个简单的挂起函数演示了基本语法。该函数可以执行长时间运行的操作,而不会阻塞调用线程。它使用 Kotlin 协程库中的 delay 函数。
package com.zetcode
import kotlinx.coroutines.*
suspend fun greet() {
delay(1000)
println("Hello from suspend function!")
}
fun main() = runBlocking {
greet()
println("Done")
}
此示例显示了一个简单的挂起函数 greet。runBlocking 协程构建器创建协程上下文。该函数使用 delay 暂停 1 秒,然后打印一条消息。
带参数的挂起函数
挂起函数可以像常规函数一样接受参数。它们也可以返回值。这使得它们对各种异步操作具有灵活性。该示例显示了一个处理输入的挂起函数。
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 协程构建器允许从常规代码调用此挂起函数。
顺序挂起调用
挂起函数可以按顺序调用其他挂起函数。执行在每个挂起点暂停。这创建了可读的异步代码,看起来像同步代码。
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 函数暂停,直到结果准备就绪。
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 错误处理。异常的工作方式与常规函数相同。这使得协程中的错误处理变得熟悉而直接。
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。
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 检查或修改它。这对于结构化并发和上下文传播很有用。
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 的 suspend 关键字,展示了如何编写和使用挂起函数。我们探讨了各种场景,包括顺序和并行执行、错误处理和上下文感知。正确使用挂起函数可以使异步代码更干净、更易于维护。
作者
列出 所有 Kotlin 教程。