Kotlin -> 普通Lambda vs 挂起Lambda

发布于:2025-08-01 ⋅ 阅读:(19) ⋅ 点赞:(0)

1. 普通Lambda vs 挂起Lambda的本质区别

1.1 普通Lambda(同步执行)

val lambda: (Int) -> String = { it.toString() }

// 编译器生成:
class Lambda$1 : Function1<Int, String> {
    override fun invoke(p1: Int): String {
        return p1.toString() // 直接返回结果
    }
}
  • 特点:
    • 同步执行:函数调用后立即返回结果
    • 不能挂起:执行过程中不会暂停
    • 简单映射:参数和返回类型直接对应
    • 单线程执行:在调用线程上同步完成

1.2 挂起Lambda(异步执行)

val suspendLambda: suspend (Int) -> String = { 
    delay(1000) // 可能挂起
    it.toString() 
}

// 编译器生成:
class SuspendLambda$1 : SuspendLambda, Function2<Int, Continuation<String>, Any?> {
    override fun invoke(p1: Int, continuation: Continuation<String>): Any? {
        // 可能返回实际结果或COROUTINE_SUSPENDED
    }
}
  • 特点:
    • 异步执行:可能不会立即返回结果
    • 可以挂起:遇到挂起点会暂停执行
    • CPS转换:需要额外的Continuation参数
    • 状态机:内部实现为状态机来处理挂起/恢复

2. CPS (Continuation Passing Style) 转换原理

2.1 什么是CPS

CPS是一种编程风格,函数不直接返回结果,而是将结果传递给一个continuation(继续执行的回调)

// 直接风格 (Direct Style)
fun add(a: Int, b: Int): Int = a + b

// CPS风格
fun addCPS(a: Int, b: Int, cont: (Int) -> Unit) {
    cont(a + b) // 将结果传递给continuation
}

2.2 Kotlin挂起函数的CPS转换

// 用户编写的挂起函数
suspend fun fetchUser(id: Int): User {
    val response = httpCall(id) // 可能挂起
    return parseUser(response)
}

// 编译器转换后的实际签名
fun fetchUser(id: Int, continuation: Continuation<User>): Any? {
    // 返回User或COROUTINE_SUSPENDED
}

3. 为什么需要额外的Continuation参数?

3.1 挂起和恢复机制

suspend fun example(): String {
    println("Before delay")
    delay(1000) // 挂起点
    println("After delay") 
    return "Done"
}

// 编译后的状态机逻辑(简化)
fun example(continuation: Continuation<String>): Any? {
    val sm = continuation as? ExampleStateMachine ?: ExampleStateMachine(continuation)
    
    when (sm.label) {
        0 -> {
            println("Before delay")
            sm.label = 1
            val result = delay(1000, sm) // 传递状态机作为continuation
            if (result == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
            // 如果没挂起,继续执行
        }
        1 -> {
            println("After delay")
            return "Done"
        }
    }
}

3.2 Continuation的作用

interface Continuation<in T> {
    val context: CoroutineContext
    fun resumeWith(result: Result<T>) // 恢复执行的回调
}
  • Continuation负责:
    • 保存执行状态:当前执行到哪个步骤
    • 恢复执行:异步操作完成后继续执行
    • 异常处理:传递异步操作中的异常
    • 上下文管理:携带协程上下文信息

4. 详细的转换规则对比

4.1 普通Lambda转换规则

// 原始类型:(P1, P2, ..., Pn) -> R
// 转换为:Function(n)<P1, P2, ..., Pn, R>

val lambda1: () -> Int = { 42 }
// 转换为:Function0<Int>

val lambda2: (String) -> Int = { it.length }
// 转换为:Function1<String, Int>

val lambda3: (Int, String) -> Boolean = { i, s -> i > 0 && s.isNotEmpty() }
// 转换为:Function2<Int, String, Boolean>

4.2 挂起Lambda转换规则

// 原始类型:suspend (P1, P2, ..., Pn) -> R
// 转换为:Function(n+1)<P1, P2, ..., Pn, Continuation<R>, Any?>

val suspendLambda1: suspend () -> Int = { 
    delay(1000)
    42 
}
// 转换为:Function1<Continuation<Int>, Any?>

val suspendLambda2: suspend (String) -> Int = { 
    delay(1000)
    it.length 
}
// 转换为:Function2<String, Continuation<Int>, Any?>

val suspendLambda3: suspend (Int, String) -> Boolean = { i, s ->
    delay(1000)
    i > 0 && s.isNotEmpty()
}
// 转换为:Function3<Int, String, Continuation<Boolean>, Any?>

4.3 带接收者的Lambda转换

// 普通带接收者Lambda
val receiverLambda: String.() -> Int = { this.length }
// 转换为:Function1<String, Int>

// 挂起带接收者Lambda
val suspendReceiverLambda: suspend String.() -> Int = { 
    delay(1000)
    this.length 
}
// 转换为:Function2<String, Continuation<Int>, Any?>

5. 为什么返回类型是Any?而不是具体类型?

5.1 挂起函数的两种返回情况

suspend fun maybeSupsend(): String {
    return if (someCondition) {
        "immediate result" // 立即返回
    } else {
        delay(1000) // 挂起,稍后恢复
        "delayed result"
    }
}

// 编译后
fun maybeSupsend(cont: Continuation<String>): Any? {
    return if (someCondition) {
        "immediate result" // 返回String
    } else {
        // 启动异步操作,返回挂起标记
        return COROUTINE_SUSPENDED // 返回特殊标记
    }
}

5.2 COROUTINE_SUSPENDED标记

// kotlin.coroutines.intrinsics
public val COROUTINE_SUSPENDED: Any = CoroutineSingletons.COROUTINE_SUSPENDED

// 使用示例
fun someFunction(): Any? {
    return when {
        canReturnImmediately -> "actual result"
        needsToSuspend -> COROUTINE_SUSPENDED
        else -> null
    }
}

6. 完整的对比示例

6.1 普通Lambda的完整流程

val lambda: (Int) -> String = { it.toString() }

// 编译生成
class Lambda$1 : Function1<Int, String> {
    override fun invoke(p1: Int): String {
        return p1.toString()
    }
}

// 调用流程
val result = lambda(42) // 直接调用invoke,立即得到结果 "42"

6.2 挂起Lambda的完整流程

val suspendLambda: suspend (Int) -> String = { 
    delay(1000)
    it.toString() 
}

// 编译生成
class SuspendLambda$1 : SuspendLambda, Function2<Int, Continuation<String>, Any?> {
    var label = 0
    var p$0: Int = 0 // 保存参数
    
    override fun invoke(p1: Int, continuation: Continuation<String>): Any? {
        val sm = create(p1, continuation) as SuspendLambda$1
        return sm.invokeSuspend(Unit)
    }
    
    override fun invokeSuspend(result: Result<Any?>): Any? {
        when (label) {
            0 -> {
                label = 1
                val delayResult = delay(1000, this)
                if (delayResult == COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED
            }
            1 -> {
                return p$0.toString()
            }
        }
    }
}

// 调用流程
// 1. 调用invoke(42, continuation)
// 2. 可能返回COROUTINE_SUSPENDED(挂起)
// 3. 异步操作完成后,通过continuation.resumeWith恢复
// 4. 最终得到结果 "42"

7. 总结:根本区别

特性 普通Lambda 挂起Lambda
执行模式 同步,立即返回 异步,可能挂起
参数转换 直接映射 额外添加Continuation
返回类型 保持原类型 改为Any?(支持挂起标记)
内部实现 简单函数调用 状态机 + CPS
继承关系 Function接口 SuspendLambda + Function接口
调用约定 直接调用 CPS调用约定

网站公告

今日签到

点亮在社区的每一天
去签到