Kotlin 协程源代码泛读:suspend 函数

使用 Kotlin 协程绕不开 suspend(挂起)函数,那么 suspend 函数的本质是什么?

在这篇文章中我尝试讲讲我所理解的 suspend 函数

我们仍然可以通过反编译 Kotlin 代码来窥探 suspend 函数的细节

kotlin 复制代码
fun main() {
    runBlocking {
        doSuspend()
    }
}

suspend fun doSuspend() {
    delay(1000L)
    println("doSuspend")
}

这段简单的代码反编译成 Java 代码后是这个样子的

less 复制代码
@Nullable
public static final Object doSuspend(@NotNull Continuation $completion) {
   ...
}

emm, 具体的实现我们先不看,因为信息量有点多...,容易被劝退

首先,我们可以确认的是 suspend 函数也是一个普通的函数

其次,它的内部实现被 Kotlin 编译器插件动过手脚...,帮我们生成了很多样板代码

再次,编译器插入了一个类型为 Continuation 的 completion 参数,这个 Continuation 在之前的系列文章提及过,可以想象成是对状态机(State Machine)的封装,而(suspend)函数可以认为是一个状态机

参数的命名也是有讲究的,completion,即完成的意思,最终执行结果通过它回调出去

你可以联想一下在「史前」时代我们如何封装一个异步方法

kotlin 复制代码
fun doSuspend(callback: Callback) {
   ...
}

给函数增加一个 callback,以便在它干完事后通知一下我们,所以你也可以将 Continuation 和 Callback 进行类比,降低接收新事物的难度

最后总结一下:suspend 函数是一个带有 completion (回调) 尾部参数的函数

基于这个理解,我们可以写出下面这个搞怪的代码,当然只是为了验证我们的推断

kotlin 复制代码
fun main() {
    (::doSuspendMock as (suspend () -> Unit)).startCoroutine(
        Continuation(
            context = EmptyCoroutineContext,
            resumeWith = {
                println("ok, get!")
            }
        )
    )
}

fun doSuspendMock(completion: Continuation<Unit>) {
    println("doSuspendMock")
}

doSuspendMock 并不是 suspend 函数(没有 suspend 关键字),我可以把它强制转换成 suspend fun

startCoroutine 是 Kotlin 协程框架提供的方法,用于执行一个 suspend 方法(启动协程),这里我们手动创建了一个 Continuation(回调)用于接收 suspend 方法执行结果:

arduino 复制代码
doSuspendMock
ok, get!

ok,我们接着看看之前说的信息量比较大的部分...

这段代码初见不好理解,需要你把自己想象成一台「电脑」,然后沉浸式的体验一下执行流程

任何的技术文章的解读都不及你自己静下心来在脑袋里跑一遍,所以我直接在代码上做点关键性的注释

php 复制代码
   @Nullable
   public static final Object doSuspend(@NotNull Continuation $completion) {
      Continuation $continuation;
      // 这个便签内的代码用于创建和维护 Continuation(Callback)
      label20: {
         if ($completion instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)$completion;
            if (($continuation.label & Integer.MIN_VALUE) != 0) {
               $continuation.label -= Integer.MIN_VALUE;
               break label20;
            }
         }
         // 首次执行时,上面那个 if 条件不满足,所以会创建一个 Continuation 对象
         // 它会贯穿整个 suspend 函数的生命周期
         // 想想为什么这里需要一个 Continuation?为了接收 delay 的回调
         // delay 也是一个 suspend 函数,在它执行完之后通知我们继续执行...
         $continuation = new ContinuationImpl($completion) {
            // $FF: synthetic field
            Object result;
            // 你可以把这个 label 想象成状态机的状态,初始值为 0
            int label;
            // 你可以把这个 invokeSuspend 函数想象成状态机在执行状态迁移 
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               // 所谓的状态转移,就是重复的调用自个儿 doSuspend
               return SuspendKt.doSuspend((Continuation)this);
            }
         };
      }
      // 这个 switch 就是所谓的状态机,因为我们只调用了一个 suspend 函数(delay)
      // 分支会比较少,同时结构也比较简单
      Object $result = $continuation.result;
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch ($continuation.label) {
         case 0:
            ResultKt.throwOnFailure($result);
            // 将状态设置 1,下次回调 doSuspend 的时候,走 case 1
            $continuation.label = 1;
            // delay 函数也是一个 suspend 函数,continuation 用于接收回调
            // 这里是实现挂起的细节,可以细品
            if (DelayKt.delay(1000L, $continuation) == var3) {
               return var3;
            }
            break;
         case 1:
            ResultKt.throwOnFailure($result);
            break;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      // ok,我们的代码终于出现了
      System.out.println("doSuspend");
      return Unit.INSTANCE;
   }

总结 suspend 函数由两部分组成,无论多复杂都是如此:

  • Continuation(回调)初始化
  • 状态机

suspend 函数内部如果调用别的 suspend 函数,它们会将 caller「分割」成多个状态

suspend 函数的执行就是在状态机内部打转,然后通过 completion 回调出去

顺便提一句谨慎使用 suspend,Kotlin 编译器插件夹带的私货有点多

Android Studio/Intellij IDE 中如果你声明的 suspend 函数并不会挂起它会建议你删除 suspend 关键字!

相关推荐
猿来入此小猿3 分钟前
基于SSM实现的健身房系统功能实现十六
java·毕业设计·ssm·毕业源码·免费学习·猿来入此·健身平台
goTsHgo40 分钟前
Spring Boot 自动装配原理详解
java·spring boot
卑微的Coder1 小时前
JMeter同步定时器 模拟多用户并发访问场景
java·jmeter·压力测试
pjx9871 小时前
微服务的“导航系统”:使用Spring Cloud Eureka实现服务注册与发现
java·spring cloud·微服务·eureka
多多*2 小时前
算法竞赛相关 Java 二分模版
java·开发语言·数据结构·数据库·sql·算法·oracle
爱喝酸奶的桃酥2 小时前
MYSQL数据库集群高可用和数据监控平台
java·数据库·mysql
唐僧洗头爱飘柔95272 小时前
【SSM-SSM整合】将Spring、SpringMVC、Mybatis三者进行整合;本文阐述了几个核心原理知识点,附带对应的源码以及描述解析
java·spring·mybatis·springmvc·动态代理·ioc容器·视图控制器
骑牛小道士2 小时前
Java基础 集合框架 Collection接口和抽象类AbstractCollection
java
alden_ygq3 小时前
当java进程内存使用超过jvm设置大小会发生什么?
java·开发语言·jvm
triticale3 小时前
【Java】网络编程(Socket)
java·网络·socket