深入理解 Kotlin 协程的挂起与恢复机制

Kotlin 协程是一种轻量级的并发编程工具,它通过挂起函数和协程状态机来实现异步编程。本文将深入探讨 Kotlin 协程的挂起与恢复机制,帮助你更好地理解协程的工作原理。

什么是挂起?

挂起(suspend)是指协程在执行过程中遇到挂起点时,暂停当前协程的执行,并保存其状态,以便稍后恢复执行。挂起点通常是一些耗时操作,如网络请求、I/O 操作或定时器。

挂起函数的实现

挂起函数是协程中的核心概念,它们会在执行过程中挂起协程,并在挂起操作完成后恢复执行。以下是一个常见的挂起函数 delay 的简化实现(伪代码):

kotlin 复制代码
suspend fun delay(timeMillis: Long) {
    // 挂起当前协程,等待指定的时间
    return suspendCoroutineUninterceptedOrReturn { continuation ->
        // 启动一个定时器,在指定时间后恢复协程
        Timer().schedule(timeMillis) {
            continuation.resume(Unit)
        }
        // 返回挂起标记,表示协程已挂起
        IntrinsicsKt.getCOROUTINE_SUSPENDED()
    }
}

delay 函数中,suspendCoroutineUninterceptedOrReturn 是一个低级别的挂起函数,它会挂起当前协程,并返回 IntrinsicsKt.getCOROUTINE_SUSPENDED(),表示协程已挂起。挂起后,协程的执行会暂停,直到定时器触发并恢复协程。

挂起和恢复的具体体现

让我们通过一个示例代码来展示挂起和恢复的具体过程:

kotlin 复制代码
import kotlinx.coroutines.*

fun main() = runBlocking {
    val repo = UserRepository()
    val user1 = async {
        repo.getUserById(1)
    }
    val user2 = async {
        repo.getUserById(2)
    }
    log(user1.await())
    log(user2.await())
    log("end")
}

class UserRepository {
    suspend fun getUserById(id: Int): User {
        // 切换到 IO 线程池
        return withContext(Dispatchers.IO) {
            // 挂起当前协程,等待 1 秒
            delay(1000L)
            // 恢复执行,返回结果
            User(id, "User $id")
        }
    }
}

data class User(val id: Int, val name: String)

fun log(message: Any?) {
    println("[${Thread.currentThread().name}] $message")
}

协程状态机

Kotlin 协程的挂起和恢复是通过协程状态机来实现的。每个挂起函数都会生成一个状态机,该状态机会在挂起点保存协程的当前状态,并在恢复时继续执行。

以下是一个简化的 invokeSuspend 方法的实现,展示了挂起和恢复的过程:

kotlin 复制代码
public final Object getUserById(final int id, @NotNull Continuation $completion) {
    return BuildersKt.withContext((CoroutineContext)Dispatchers.getIO(), (Function2)(new Function2((Continuation)null) {
        int label;

        @Nullable
        public final Object invokeSuspend(@NotNull Object $result) {
            Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch (this.label) {
                case 0:
                    // 初始状态,检查是否有异常
                    ResultKt.throwOnFailure($result);
                    // 设置下次恢复时的状态
                    this.label = 1;
                    // 调用 delay 方法,挂起协程
                    if (DelayKt.delay(1000L, this) == var2) {
                        // 返回挂起标记,协程暂停执行
                        return var2;
                    }
                    break;
                case 1:
                    // 恢复状态,检查是否有异常
                    ResultKt.throwOnFailure($result);
                    break;
                default:
                    throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            // 返回结果
            return new User(id, "User " + id);
        }

        @NotNull
        public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function2 var3 = new <anonymous constructor>(completion);
            return var3;
        }

        public final Object invoke(Object var1, Object var2) {
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
        }
    }), $completion);
}

详细执行过程

  1. 启动 runBlocking

    • runBlocking 启动一个新的协程,并阻塞当前线程,直到协程完成。
  2. 启动 async 协程

    • async 启动两个新的协程,分别调用 repo.getUserById(1)repo.getUserById(2)
  3. 调用 getUserById

    • getUserById 方法被调用,进入 withContext 块,切换到 Dispatchers.IO 线程池。
    • invokeSuspend 方法被调用,label 的初始值为 0
  4. 第一次执行 invokeSuspend

    • label0,进入 case 0
    • 调用 ResultKt.throwOnFailure($result) 检查是否有异常。
    • 设置 label1,表示下次恢复时从 case 1 开始执行。
    • 调用 DelayKt.delay(1000L, this) 模拟网络请求的延迟。
    • delay 方法返回 IntrinsicsKt.getCOROUTINE_SUSPENDED(),表示协程已挂起。
    • invokeSuspend 方法返回 IntrinsicsKt.getCOROUTINE_SUSPENDED(),协程暂停执行。
  5. 挂起状态

    • 协程挂起,等待 delay 方法完成。
  6. 恢复执行

    • delay 方法完成后,协程恢复执行。
    • 恢复时,invokeSuspend 方法再次被调用,这次 label 的值为 1
  7. 第二次执行 invokeSuspend

    • label1,进入 case 1
    • 调用 ResultKt.throwOnFailure($result) 检查是否有异常。
    • 协程继续执行,创建并返回一个新的 User 对象:new User(id, "User " + id)
  8. 等待 async 结果

    • user1.await()user2.await() 分别等待 user1user2 协程的结果。
    • await 方法会挂起当前协程,直到 user1user2 协程完成并返回结果。
  9. 打印结果

    • log(user1.await())log(user2.await()) 分别打印 user1user2 的结果。
    • log("end") 打印 "end"。

总结

挂起在函数中的具体体现是通过返回 IntrinsicsKt.getCOROUTINE_SUSPENDED() 来实现的。当协程遇到挂起点时,会返回这个特殊的标记值,表示协程已挂起。挂起后,协程的执行会暂停,当前的执行状态会被保存下来,等待挂起操作完成后再恢复执行。恢复时,协程会从挂起点继续执行,恢复之前保存的状态。

相关推荐
*星星之火*1 小时前
【GPT入门】第5课 思维链的提出与案例
android·gpt
EasyCVR1 小时前
EasyRTC嵌入式视频通话SDK的跨平台适配,构建web浏览器、Linux、ARM、安卓等终端的低延迟音视频通信
android·arm开发·网络协议·tcp/ip·音视频·webrtc
韩家老大2 小时前
RK Android14 在计算器内输入特定字符跳转到其他应用
android
张拭心4 小时前
2024 总结,我的停滞与觉醒
android·前端
夜晚中的人海4 小时前
【C语言】------ 实现扫雷游戏
android·c语言·游戏
ljx14000525506 小时前
Android AudioFlinger(一)——初识AndroidAudio Flinger
android
ljx14000525506 小时前
Android AudioFlinger(四)—— 揭开PlaybackThread面纱
android
Codingwiz_Joy6 小时前
Day04 模拟原生开发app过程 Androidstudio+逍遥模拟器
android·安全·web安全·安全性测试
叶羽西6 小时前
Android15 Camera框架中的StatusTracker
android·camera框架
梦中千秋6 小时前
安卓设备root检测与隐藏手段
android