Kotlin 中的 suspend(挂起函数)

了解 Kotlin 中的 suspend(挂起函数),我会从核心概念、特性、使用规则到实际场景为你全面解析:

一、suspend 关键字的核心定义

suspend(译为「挂起」)是 Kotlin 中用于标记「可暂停执行」函数的关键字 ,其核心作用是:允许函数在执行过程中暂时挂起(释放线程资源),等待某个耗时操作(如网络请求、数据库读写、文件IO)完成后,再恢复执行后续代码,且整个过程不会阻塞当前线程

简单来说,挂起函数就是「能暂停、能恢复、不阻塞」的特殊函数。

二、suspend 函数的核心特性:挂起 ≠ 阻塞

这是 suspend 最关键的区别,必须明确:

特性 挂起(suspend 阻塞(普通耗时操作)
线程资源 挂起时释放线程,线程可去执行其他任务 阻塞时占用线程,线程无法执行其他任务
执行状态 函数执行被暂停,后续代码需等待挂起恢复后执行 函数一直占用线程,直到耗时操作完成才返回
性能影响 高并发场景下资源利用率极高(无线程闲置) 高并发场景下易出现线程耗尽,性能低下

例如:网络请求用挂起函数,线程在等待服务器响应时会被释放,去处理其他接口请求;而用普通阻塞函数,线程会一直闲置等待响应,造成资源浪费。

三、suspend 函数的使用约束:不能在普通函数中直接调用

挂起函数有严格的调用规则:suspend 函数只能在 3 种上下文中调用,不能直接在普通(非挂起)函数中调用:

  1. 在另一个 suspend 函数中调用(最直接的方式)

    kotlin 复制代码
    // 挂起函数1:模拟网络请求
    suspend fun fetchUserInfo(): String {
        // 模拟耗时操作(实际是 Retrofit/Room 等提供的挂起API)
        delay(1000) // delay 是 Kotlin 内置的挂起函数(非阻塞)
        return "用户信息"
    }
    
    // 挂起函数2:可直接调用挂起函数1
    suspend fun loadData(): String {
        val userInfo = fetchUserInfo() // 合法:挂起函数调用挂起函数
        return "加载完成:$userInfo"
    }
  2. 在协程(Coroutine)体中调用 (最常用的方式)

    协程是 suspend 函数的「执行容器」,所有挂起函数最终都要在协程中执行。常用的协程启动方式有 launchasync 等(需依赖 kotlinx-coroutines-core 库):

    kotlin 复制代码
    import kotlinx.coroutines.GlobalScope
    import kotlinx.coroutines.launch
    
    // 普通函数
    fun main() {
        // 协程体中调用挂起函数,合法
        GlobalScope.launch {
            val data = loadData() // 合法:协程体内调用挂起函数
            println(data)
        }
    
        // 防止程序提前退出(仅演示用,实际开发不推荐 GlobalScope)
        Thread.sleep(2000)
    }
  3. 在带有 CoroutineScope 的扩展函数/上下文类中调用

    如 Android 中的 lifecycleScope.launchviewModelScope.launch,本质也是在协程体中执行,间接支持挂起函数调用。

错误示例:普通函数直接调用挂起函数(编译报错)

kotlin 复制代码
fun normalFunction() {
    val userInfo = fetchUserInfo() // 编译错误:Suspend function 'fetchUserInfo' should be called only from a coroutine or another suspend function
}

四、suspend 函数的底层支撑:挂起函数的本质

suspend 函数并非 Kotlin 语法糖那么简单,其底层依赖 CPS(Continuation-Passing Style,延续传递风格) 实现:

  1. 当 Kotlin 编译器遇到 suspend 关键字时,会自动对函数进行CPS 转换 :给函数隐式添加一个 Continuation 类型的参数(「延续对象」,用于保存函数挂起前的执行状态,如局部变量、代码执行位置等)。
  2. 当函数执行到耗时操作需要挂起时,会将当前执行状态保存到 Continuation 中,然后释放线程资源;当耗时操作完成后,通过 Continuation 恢复之前的执行状态,继续执行函数后续代码。
  3. 这种转换是编译器自动完成的,开发者无需手动处理 Continuation 对象,只需关注业务逻辑。

五、suspend 函数的典型使用场景

suspend 函数几乎专门用于处理耗时的异步操作,常见场景包括:

  1. 网络请求 :Retrofit 支持挂起函数,替代传统的 Callback 回调

    kotlin 复制代码
    // Retrofit 接口定义(挂起函数)
    interface ApiService {
        @GET("user/info")
        suspend fun getUserInfo(): UserResponse // 无需 Callback,直接返回结果
    }
  2. 本地存储操作 :Room 数据库支持挂起函数,避免主线程阻塞

    kotlin 复制代码
    @Dao
    interface UserDao {
        @Query("SELECT * FROM user WHERE id = :userId")
        suspend fun getUserById(userId: Int): User? // 挂起函数查询数据库
    }
  3. 文件 IO 操作:大文件读写、解压等耗时操作,用挂起函数不阻塞线程

  4. 延时任务 :Kotlin 内置 delay(long) 挂起函数(非阻塞延时,替代 Thread.sleep()

六、补充:suspend 函数的额外说明

  1. suspend 函数可以有返回值、参数,也可以是抽象函数(用于接口定义)
  2. suspend 关键字仅标记函数可挂起,不保证函数一定是异步执行(需内部调用真正的挂起逻辑,如 delay、Retrofit 挂起 API 等)
  3. 挂起函数的恢复执行线程,由其所在协程的 Dispatcher 决定(如 Dispatchers.IODispatchers.Main 等)

总结

  1. suspend 标记可暂停、可恢复的挂起函数,核心是「不阻塞线程」;
  2. 挂起 ≠ 阻塞,挂起时释放线程资源,阻塞时占用线程资源;
  3. 约束:不能直接在普通函数中调用,仅可在其他挂起函数、协程体中使用;
  4. 底层:依赖 CPS 转换和 Continuation 对象实现挂起与恢复;
  5. 场景:专门用于处理网络、数据库、文件 IO 等耗时异步操作。
相关推荐
周亚鑫2 小时前
vue3 js代码混淆
开发语言·javascript·ecmascript
陳10302 小时前
C++:vector(1)
开发语言·c++
棉晗榜2 小时前
WPF将程序集里面嵌入的资源文件下载到本机磁盘中,将项目中的文件下载到桌面
开发语言·wpf
花卷HJ2 小时前
Android 下载管理器封装实战:支持队列下载、取消、进度回调与自动保存相册
android·java
人道领域2 小时前
【零基础学java】(Map集合)
java·开发语言
杀死那个蝈坦2 小时前
JUC并发编程day1
java·开发语言
lly2024062 小时前
SQLite Alter 命令详解
开发语言
沃斯堡&蓝鸟2 小时前
DAY33 类的装饰器
开发语言·python
小白学大数据2 小时前
海量小说数据采集:Spark 爬虫系统设计
大数据·开发语言·爬虫·spark