深入理解 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() 来实现的。当协程遇到挂起点时,会返回这个特殊的标记值,表示协程已挂起。挂起后,协程的执行会暂停,当前的执行状态会被保存下来,等待挂起操作完成后再恢复执行。恢复时,协程会从挂起点继续执行,恢复之前保存的状态。

相关推荐
aningxiaoxixi1 小时前
Android Studio 之基础代码解析
android·ide·android studio
A-花开堪折1 小时前
Android7 Input(十)View 处理Input事件pipeline
android·嵌入式硬件
Shujie_L2 小时前
Android基础回顾】六:安卓显示机制Surface 、 SurfaceFlinger、Choreographer
android
海棠一号3 小时前
Android Settings 数据库生成、监听与默认值配置
android·数据库
雨白3 小时前
Fragment 入门教程:从核心概念到实践操作
android
烈焰晴天3 小时前
使用ReactNative加载Svga动画支持三端【Android/IOS/Harmony】
android·react native·ios
阿幸软件杂货间3 小时前
PPT转图片拼贴工具 v2.0
android·python·powerpoint
sg_knight4 小时前
Flutter嵌入式开发实战 ——从树莓派到智能家居控制面板,打造工业级交互终端
android·前端·flutter·ios·智能家居·跨平台
Digitally4 小时前
如何轻松将视频从安卓设备传输到电脑?
android·电脑·音视频
Dola_Pan5 小时前
Android四大组件通讯指南:Kotlin版组件茶话会
android·开发语言·kotlin