在 Kotlin 中,suspend 关键字是 协程(Coroutines) 的核心概念,用于标记 挂起函数。让我详细解释它的作用:
1. 基本概念
suspend 关键字标记的函数可以在不阻塞线程的情况下暂停和恢复执行。
// 普通的挂起函数
suspend fun fetchUserData(): User {
// 可以在这里调用其他挂起函数
return api.getUser()
}
2. 核心作用
异步编程的同步写法
// 传统回调方式(回调地狱)
fun loadData() {
api.getUser { user ->
api.getProfile(user.id) { profile ->
api.getFriends(profile.id) { friends ->
// 处理结果
}
}
}
}
// 使用 suspend 函数(同步式写法)
suspend fun loadData() {
val user = api.getUser() // 挂起点
val profile = api.getProfile(user.id) // 挂起点
val friends = api.getFriends(profile.id) // 挂起点
// 处理结果
}
3. 工作原理
状态机机制
// 你写的代码
suspend fun fetchData(): String {
val user = getUser()
val profile = getProfile(user)
return "Data: $user, $profile"
}
// 编译器生成的伪代码(简化)
class FetchDataStateMachine {
var state = 0
var user: User? = null
var profile: Profile? = null
fun doWork(completion: Continuation<String>) {
when (state) {
0 -> {
state = 1
getUser(completion) // 挂起
return
}
1 -> {
user = result
state = 2
getProfile(user!!, completion) // 挂起
return
}
2 -> {
profile = result
completion.resume("Data: $user, $profile")
}
}
}
}
4. 适用场景
网络请求
suspend fun fetchUserFromApi(): User {
return withContext(Dispatchers.IO) {
// 在IO线程执行网络请求
apiService.getUser()
}
}
数据库操作
suspend fun saveUserToDb(user: User) {
withContext(Dispatchers.IO) {
database.userDao().insert(user)
}
}
文件操作
suspend fun readFileContent(path: String): String {
return withContext(Dispatchers.IO) {
File(path).readText()
}
}
5. 调用规则
挂起函数只能在协程或其他挂起函数中调用
// 正确:在协程中调用
fun main() = runBlocking {
val user = fetchUserData() // 正确
}
// 正确:在其他挂起函数中调用
suspend fun processUser() {
val user = fetchUserData() // 正确
}
// 错误:在普通函数中直接调用
fun regularFunction() {
// fetchUserData() // 编译错误!
}
6. 与协程构建器的关系
// 常见的协程构建器
fun main() {
// 1. runBlocking - 阻塞当前线程直到协程完成
runBlocking {
fetchUserData()
}
// 2. launch - 启动新协程,不阻塞当前线程
GlobalScope.launch {
fetchUserData()
}
// 3. async - 启动有返回值的协程
val deferred = GlobalScope.async {
fetchUserData()
}
}
7.实际示例
组合多个挂起函数
suspend fun loadUserProfile(userId: String): UserProfile {
// 顺序执行
val user = getUser(userId)
val profile = getProfile(user.profileId)
val friends = getFriends(user.id)
return UserProfile(user, profile, friends)
}
// 并发执行
suspend fun loadUserProfileConcurrent(userId: String): UserProfile {
// 使用 async 并发执行
val userDeferred = async { getUser(userId) }
val profileDeferred = async { getProfile(userId) }
val friendsDeferred = async { getFriends(userId) }
// 等待所有结果
return UserProfile(
userDeferred.await(),
profileDeferred.await(),
friendsDeferred.await()
)
}
8. 异常处理
suspend fun loadDataWithHandling(): Result {
return try {
val data = fetchData()
Result.Success(data)
} catch (e: Exception) {
Result.Error(e)
}
}
// 或者在协程层面处理
fun main() = runBlocking {
try {
val result = loadData()
println("Success: $result")
} catch (e: Exception) {
println("Error: ${e.message}")
}
}
9.优势总结
-
简化异步代码: 用同步写法处理异步操作
-
避免回调地狱: 消除嵌套的回调函数
-
更好的错误处理: 可以使用 try-catch
-
线程安全: 自动处理线程切换
-
资源友好: 不阻塞线程,提高资源利用率
10. 重要注意事项
-
suspend 函数不会自动在后台线程运行
-
需要在挂起函数内部使用 withContext 指定调度器
-
挂起函数应该是可以安全取消的
-
避免在挂起函数中执行阻塞操作
// 正确:明确指定调度器
suspend fun fetchData(): Data {
return withContext(Dispatchers.IO) {
// 执行IO操作
api.getData()
}
}
// 错误:没有指定调度器,仍在调用线程执行
suspend fun fetchDataWrong(): Data {
return api.getData() // 如果 api.getData() 是阻塞的,会阻塞调用线程
}
suspend 关键字是 Kotlin 协程的基石,它让异步编程变得更加直观和易于维护。
653

被折叠的 条评论
为什么被折叠?



