题目
解释Kotlin协程中的挂起函数(suspend function)及其与普通函数的区别
信息
- 类型:问答
- 难度:⭐
考点
挂起函数概念,协程基础,协程与线程区别
快速回答
挂起函数是Kotlin协程的核心概念:
- 使用
suspend关键字声明,只能在协程或其他挂起函数中调用 - 执行时不会阻塞当前线程,可自动挂起和恢复
- 普通函数会阻塞调用线程,而挂起函数通过状态机机制实现非阻塞
1. 挂起函数的核心概念
挂起函数(Suspend Function)是Kotlin协程的标志性特性:
- 声明方式:使用
suspend关键字修饰函数 - 核心能力:可以在不阻塞线程的情况下暂停执行(挂起),并在结果就绪后恢复执行
- 典型场景:网络请求、文件读写、数据库操作等耗时任务
2. 与普通函数的区别
| 特性 | 普通函数 | 挂起函数 |
|---|---|---|
| 关键字 | 无特殊关键字 | 需suspend修饰 |
| 阻塞性 | 阻塞调用线程 | 非阻塞(挂起协程但不阻塞线程) |
| 调用位置 | 任意位置 | 只能在协程或其他挂起函数中调用 |
| 执行机制 | 直接执行 | 通过状态机实现挂起/恢复 |
3. 代码示例
// 普通函数(阻塞线程)
fun fetchDataNormal(): String {
Thread.sleep(2000) // 模拟阻塞
return "Data"
}
// 挂起函数(非阻塞)
suspend fun fetchDataSuspend(): String {
delay(2000) // 非阻塞挂起
return "Data"
}
// 在协程中调用
fun main() = runBlocking {
launch {
println("Start")
val data = fetchDataSuspend() // 挂起点
println("Received: $data") // 恢复执行
}
println("After launch")
}
输出顺序:
1. Start
2. After launch
3. (2秒后) Received: Data
4. 工作原理
- 状态机机制:编译器将挂起函数转换为带有状态机的回调代码
- Continuation续体:通过
Continuation对象保存挂起时的上下文和恢复点 - 线程释放:挂起时释放底层线程,线程可执行其他任务
5. 最佳实践
- 为所有耗时操作定义挂起函数(如Retrofit/Room已支持
suspend) - 避免在挂起函数中使用
Thread.sleep(),改用delay() - 主线程安全:挂起函数可在主线程调用而不阻塞UI
6. 常见错误
- 错误1:在非协程作用域调用挂起函数
// 错误示例:普通函数中直接调用suspend函数
fun invalidCall() { fetchDataSuspend() } - 错误2:误用阻塞操作
suspend fun wrongFetch() { Thread.sleep(2000) // 应改用delay() }
7. 扩展知识
- 底层原理:通过CPS(Continuation-Passing Style)转换实现
- 协程调度器:决定协程在哪个线程执行(Dispatchers.IO/Main/Default)
- 结构化并发:通过CoroutineScope管理协程生命周期