源解 Kotlin 协程

1,559 阅读6分钟

前置知识

CPS

Continuation Passing Style(续体传递风格): 约定一种编程规范,函数不直接返回结果值,而是在函数最后一个参数位置传入一个 callback 函数参数,并在函数执行完成时通过 callback 来处理结果。回调函数 callback 被称为续体(Continuation),它决定了程序接下来的行为,整个程序的逻辑通过一个个 Continuation 拼接在一起。

简单理解就是开发中我们常用的 callback 方式来接收返回的结果,只不过这里改名叫“续体”。

Kotlin 编译器处理协程体

我们用 kotlin 写协程代码在编译阶段会经过 kotlin 编译器处理,其会对协程相关逻辑进行拓展和调整。在接下来进行“通过实例源码解读”时,要知道如何查看 kotlin 协程代码编译。

fun init(context: Context) {
  MainScope().launch {
    //...
  }
}

如上面代码在经过 kotlin 编译器处理后会是什么样子呢?

  • 首先将 kotlin 代码转成 bytecode

image.png

  • 然后再将 bytecode 转成 java

image.png
得到的就是如下代码:

public final void init(@NotNull final Context context) {
  
  BuildersKt.launch$default(
    CoroutineScopeKt.MainScope(), 
    (CoroutineContext)null, 
    (CoroutineStart)null, 
    (Function2)(new Function2((Continuation)null) {
        //....
      }), 
    3, 
    (Object)null);
}

可以看到,我们写的 MainScope().launch {} 被转换成了 BuildersKt.launch$default(),其中第四个参数尤其关键,它就是我们的协程体(即 launch{} 部分的逻辑)。它是一个 Function2 的匿名对象,是不是看不懂?这里就好需要回到上一步(kotlin 转 bytecode)来看看。
image.png
标记 1、标记 2 共同完成了一件事,创建 FlutterManagerinitinit1 对象,然后检查其类型是否为 Function2。用 java 伪代码表示

Object object = new FlutterManager$init$1(context, flutterSoConfig, continuation);
object instanceof Function2

标记3 就是上面 bytecode 转 java 后的代码块了,其中第四个入参为上面的 object。
可以发现 bytecode 中出现了转成 java 后没有的 FlutterManagerinitinit1 类,其实它就是转成 java 后的 Function2,其继承自 SuspendLambda。可以继续在 bytecode 中验证这一说法。
image.png

Continuation 继承关系

- Continuation: 续体,恢复协程的执行
 - BaseContinuationImpl: 实现 resumeWith(Result) 方法,控制状态机的执行,定义了 invokeSuspend 抽象方法
  - ContinuationImpl: 增加 intercepted 拦截器,实现线程调度等
   - SuspendLambda: 封装协程体代码块
    - FlutterManager$init$1 协程体代码块生成的子类: 实现 invokeSuspend 方法,其内实现状态机流转逻辑

实例源解

object FlutterManager{

  fun init(context: Context) {
    val flutterSoUrl = context.assets.open("flutterso.json").readBytes().decodeToString()
    val flutterConfig =
    Gson().fromJsonProxy(flutterSoUrl, FlutterSOConfig::class.java) ?: return
    MainScope().launch {
      val flutterSoFile = downloadDynamicSO(context, DownloadConfig(flutterConfig.libflutter.url, context.getDir("flutterso", Context.MODE_PRIVATE).absolutePath)
          .apply {
            fileName = FLUTTER_ENGINE_SO_NAME
          })?.let { File(it) }
      val appSoFile = downloadDynamicSO(context, DownloadConfig(flutterConfig.libapp.url, context.getDir("flutterso", Context.MODE_PRIVATE).absolutePath)
          .apply {
            fileName = FLUTTER_APP_SO_NAME
          })?.let { File(it) }
      if (flutterSoFile != null && appSoFile != null) {
        loadAndInitFlutter(context, flutterSoFile.parentFile, appSoFile.absolutePath)
      }
    }
  }

  private suspend fun downloadDynamicSO(
    context: Context,
    downloadConfig: DownloadConfig
  ): String? {
    return suspendCoroutine {
      DownloadManager.instance.start(
        context,
        downloadConfig,
        object : IDownloadListener {
          override fun onSuccess(url: String?, savePath: Uri?) {
            super.onSuccess(url, savePath)
            it.resume(savePath?.path)
          }

          override fun onFailed(url: String?, throwable: Throwable) {
            super.onFailed(url, throwable)
            it.resumeWithException(throwable)
          }
        })
    }
  }
}

创建与启动

//在示例代码中只传入了 block 参数,其他均采用默认值。
// bloack 即为协程体
public fun CoroutineScope.launch(
  context: CoroutineContext = EmptyCoroutineContext,
  start: CoroutineStart = CoroutineStart.DEFAULT,
  block: suspend CoroutineScope.() -> Unit
): Job {
  //newContext = scope 上下文 + context 上下文 + Dispatchers
  val newContext = newCoroutineContext(context)
  // 默认参数,所以 coroutine = StandaloneCoroutine()
  val coroutine = if (start.isLazy)
    	LazyStandaloneCoroutine(newContext, block) 
  	else
    	StandaloneCoroutine(newContext, active = true)
  // 启动协程
  //  StandaloneCoroutine 继承自 AbstractCoroutine,这里调用的是父类 start()
  coroutine.start(start, coroutine, block)
  return coroutine
}
//注意看!
// AbstractCoroutine 继承自 Continuation
//  所以 AbstractCoroutine 及子类(StandaloneCoroutine)都是 Continuation 对象,即续体!
public abstract class AbstractCoroutine<in T>
	: JobSupport(active), Job, Continuation<T>, CoroutineScope {

	/**
	 * @params start: 		CoroutineStart.DEFAULT
	 * @params receiver:	StandaloneCoroutine
	 * @params block: 		suspend CoroutineScope.() -> Unit
	 */
	public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
		// 调用 CoroutineStart 的重载方法 invoke()
		start(block, receiver, this)
	}
}
public enum class CoroutineStart {
  DEFAULT,
  //...

  /**
   * @params block:				suspend CoroutineScope.() -> Unit
   * @params receiver:		StandaloneCoroutine
   * @params completion:	StandaloneCoroutine
   */
  public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
    when (this) {
      //此时调用到这里!!!
      DEFAULT -> block.startCoroutineCancellable(receiver, completion)
      ATOMIC -> block.startCoroutine(receiver, completion)
      UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
      LAZY -> Unit // will start lazily
    }
}

到这里可以知道默认参数情况下,调用 launch() 会先合并 context,然后创建 StandaloneCoroutine,其继承自 AbstractCoroutine,并持有合并后的 context,即包装一层成为续体(Continuation)。然后调用启动,最后执行到 (suspend R.() -> T)#startCoroutineCancellable()。
下面看真正的启动流程。

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
    receiver: R, completion: Continuation<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
) = runSafely(completion) {
        //开始真正的启动逻辑
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
    }

createCoroutineUnintercepted()

public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
    receiver: R,
    completion: Continuation<T>
): Continuation<Unit> {
    val probeCompletion = probeCoroutineCreated(completion)
    // this 指 (suspend R.() -> T)
    //  根据“前置知识 - Kotlin编译器处理线程体”、“前置知识 - Continuation继承关系”可知,
    //   this = SuspendLambda 
    //			 = ContinuationImpl 
    //			  = BaseContinuationImpl 
    //		  = Function2
    //    即调用 Function2#create()
    return if (this is BaseContinuationImpl)
        create(receiver, probeCompletion)
    else {
        createCoroutineFromSuspendFunction(probeCompletion) {
            (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
        }
    }
}
//下面代码为 MainScope().launch{} 解码后(kotlin -> bytecode ->java)的部分代码
BuildersKt.launch$default(CoroutineScopeKt.MainScope(), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
  //....
  @NotNull
  public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
    //重新创建一个新的 SuspendLambda对象 (即新的 Continuation),其持有传入的续体(即第一层续体包装)
    Function2 var3 = new <anonymous constructor>(completion);
    return var3;
  }
}), 3, (Object)null);

intercepted()

//根据上面 createCoroutineUnintercepted() 可知,this =  ContinuationImpl
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this
internal abstract class ContinuationImpl : BaseContinuationImpl {
  //执行逻辑如下:
  // 1. intercepted != null,return 调度器包装续体。
  // 2. intercepted == null,从 context 中提取 ContinuationInterceptor,
  //     调用其 interceptContinuation(),生成调度器包装续体并 return。
  // 3. intercepted == null && context 中没有 ContinuationInterceptor,则 return 原续体。
  //
  // 按照示例代码逻辑 MainScope(),其内部会注入 Dispatchers.Main(即 MainCoroutineDispatcher)
  //  这里实际调用的是 MainCoroutineDispatcher#interceptContinuation()
  public fun intercepted(): Continuation<Any?> =
    intercepted
    	?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
      	.also { intercepted = it }
}
// MainCoroutineDispatcher 继承自 CoroutineDispatcher
//  interceptContinuation() 由父类实现
public abstract class CoroutineDispatcher:ContinuationInterceptor {
  
  //返回 DispatchedContinuation(即调度器包装续体),其持有调度器(这里指 MainCoroutineDispatcher)和第二层续体包装
  public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
  	DispatchedContinuation(this, continuation)
}

resumeCancellableWith()

public fun <T> Continuation<T>.resumeCancellableWith(
    result: Result<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
    //根据上面拦截器处理逻辑,执行到这里! 
    //  即调用 DispatchedContinuation#resumeCancellableWith()
    is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
    else -> resumeWith(result)
}
internal class DispatchedContinuation<in T>(
  @JvmField val dispatcher: CoroutineDispatcher,
  @JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {

  /**
   * @params result: 	 Result.success(Unit)
   * @parans onCancellation: null
   */
  inline fun resumeCancellableWith(
    result: Result<T>,
    noinline onCancellation: ((cause: Throwable) -> Unit)?
      ) {
    val state = result.toState(onCancellation)
    //判断是否需要线程切换
    // 根据示例代码 dispatcher = MainCoroutineDispatcher
    //   MainCoroutineDispatcher 的具体实现由 HandlerContext 来完成
    // 继承关系:
 	  //   - MainCoroutineDispatcher
  	//    - HandlerDispatcher
  	// *=> - HandlerContext 
    //
    // 根据 HandlerContext#isDispatchNeeded() 逻辑 + 示例代码运行环境,此处为 false
    if (dispatcher.isDispatchNeeded(context)) {
      _state = state
      resumeMode = MODE_CANCELLABLE
      //调用调度器进行线程切换
      //  如果需要切换的话,具体实现是由 HandlerContext#dispatch() 实现,
      //   内部就是做了 Handler(Looper.getMainLooper()).post()。
      // 传入的 this = DispatchedContinuation,其继承自 DispatchedTask,
      //  DispatchedTask 顶级父类为 Runnable,其内部重写 run(),最终调用到 continuation.resume()
      dispatcher.dispatch(context, this)
    } else {
      executeUnconfined(state, MODE_CANCELLABLE) {
        if (!resumeCancelled(state)) {
          //最终调用到 continuation.resume()
          resumeUndispatchedWith(result)
        }
      }
    }
  }

  inline fun resumeUndispatchedWith(result: Result<T>) {
    withContinuationContext(continuation, countOrElement) {
        continuation.resumeWith(result)
    }
  }
}

启动逻辑代码很简短,只有一行,但却分了三步逻辑:

  1. 调用 createCoroutineUnintercepted() 创建第二层续体包装(SuspendLambda),其持有接受者和第一层续体包装(StandaloneCoroutine);
  2. 调用 intercepted(),进行拦截器(或者叫调度器)处理(例如,线程切换),此时经过处理后会生成第三层续体包装(DispatchedContinuation);
  3. 调用 resumeCancellableWith(),内部会根据调度器判断是否需要线程切换,无论是否切换,最终都会调用 continuation#resumeWith() 执行到协程体内部逻辑。

启动完结

我们接续往下看,上面分析到最后都会调用 continuation#resumeWith()。根据 DispatchedContinuation 的构造函数可知,resumeWith() 由第二层续体包装(SuspendLambda) 来实现,即 Kotlin 编译器处理生成的 Function2 实例。那么我们就看下其内部实现。

//根据“前置知识 - Continuation继承关系”可知,
//  resumeWith() 实际由 BaseContinuationImpl 实现
internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    
    public final override fun resumeWith(result: Result<Any?>) {
        var current = this
        var param = result
        while (true) {
            with(current) {
                val completion = completion!!
                val outcome: Result<Any?> =
                    try {
                        //执行协程体 invokeSuspend()
                        val outcome = invokeSuspend(param)
                        //挂起状态,直接 return
                        if (outcome === COROUTINE_SUSPENDED) return
                        //非挂起,直接返回结果
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                //如果需要,执行线程切换
                releaseIntercepted() 
                if (completion is BaseContinuationImpl) {
                    current = completion
                    param = outcome
                } else {
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }    
}

上面的代码是一套公共逻辑,我们先只关注启动后调用 resumeWith() 会怎么执行即可。首先会调用 invokeSuspend(),此方法也是在 Kotlin编译器生成的协程代码的 Function2 中实现。

BuildersKt.launch$default(CoroutineScopeKt.MainScope(), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
  Object L$0;
  int label;

  @Nullable
  public final Object invokeSuspend(@NotNull Object $result) {
    label40: {
      // var10 = 挂起状态
      Object var10 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch (this.label) {
        // 启动调用到此处!
        case 0:
          //判断传入的 result,如果为 Result.Failure,这直接抛异常。
          //  按照上面启动逻辑,此处 resut = Result.success。
          ResultKt.throwOnFailure($result);
          //准备一些参数
          //...
          //将 label 标记为 1
          this.label = 1;
          //调用 downloadDynamicSO(), 传入函数定义的参数 + 续体
          var10000 = var7.downloadDynamicSO(var8, var3, this);
          //判断执行结果是否为挂起状态,是则直接 return,不阻塞当前线程执行。
          // 根据示例代码,downloadDynamicSO() 为 suspend 修饰函数,所以此处应该为挂起状态。
          if (var10000 == var10) {
            return var10;
          }
          break;
          
       //...
      }
      return Unit.INSTANCE;
    }
    //...
  }), 3, (Object)null);

到这里,协程已经完成启动,并执行到第一个挂起函数,状态变为挂起状态。结合 BaseContinuationImpl#resumeWith() 逻辑,执行到挂起函数,并返回挂起状态,然后直接 return,启动阶段完结。

函数挂起与恢复

挂起

根据上面流程,逻辑执行到了挂起函数 downloadDynamicSO(),那么我们看下其怎么实现协程挂起与恢复的。
suspend 修饰 downloadDynamicSO(),其经过 Kotlin编译器生成如下代码:

//处理函数规定的入参外,最后额外追加了一个续体入参。
private final Object downloadDynamicSO(Context context, DownloadConfig downloadConfig, Continuation $completion) {
  //根据启动时的经验,此处为创建 SafeContinuation 对象,其持有 DispatchedContinuation 对象,DispatchedContinuation 又持有 SuspendLambda 对象
  SafeContinuation var5 = new SafeContinuation(IntrinsicsKt.intercepted($completion));
  Continuation it = (Continuation)var5;
  int var7 = false;
  //调用下载,此处是一个异步操作
  DownloadManager.Companion.getInstance().start(context, downloadConfig, (IDownloadListener)(new FlutterManager$downloadDynamicSO$2$1(it)));
  //获取执行结果,由于是异步操作,所以此处返回结果为挂起
  Object var10000 = var5.getOrThrow();
  return var10000;
}

代码比较简单,主要逻辑为:

  1. 创建一个 SafeContinuation 续体包装,持有 DispatchedContinuation 对象,最终持有的为协程体续体对象(即 SuspendLambda)。
  2. 执行异步代码逻辑。
  3. 返回结果,此时结果为挂起。

Q: 代码中并没有体现何时为挂起状态
A: 状态的管理由新创建的续体(SafeContinuation)处理。我们来看下它的内部实现,便可知上面代码在调用异步逻辑后,接着调用 SafeContinuation#getOrThrow() 返回的就是 COROUTINE_SUSPENDED。

internal actual class SafeContinuation<in T>
internal actual constructor(
  private val delegate: Continuation<T>,
  initialResult: Any?
) : Continuation<T>, CoroutineStackFrame {
  //未携带状态的构造函数,默认状态为 UNDECIDED
	internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)

  // 构造时未赋值状态时,默认初始状态为 UNDECIDED
  @Volatile
  private var result: Any? = initialResult
  
  internal actual fun getOrThrow(): Any? {
    var result = this.result // atomic read
    //如果默认状态,则更新状态为 COROUTINE_SUSPENDED
    if (result === UNDECIDED) {
        if (RESULT.compareAndSet(this, UNDECIDED, COROUTINE_SUSPENDED)) return COROUTINE_SUSPENDED
        result = this.result // reread volatile var
    }
    //状态更新
    return when {
        result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream
        result is Result.Failure -> throw result.exception
        else -> result // either COROUTINE_SUSPENDED or data
    }
  }
}

恢复

Q: 上面异步逻辑执行完成后(即下载完成),代码如何继续执行的呢?
A: 核心点就在下载完成后调用 continuation.resume()。我们继续来看下 downloadDynamicSO 经 Kotlin 编译器处理后生成的类。

private final Object downloadDynamicSO(Context context, DownloadConfig downloadConfig, Continuation $completion) {
  //...
  //调用下载,最后注入一个下载监听对象,该对象持有 SafeContinuation 续体对象
  DownloadManager.Companion.getInstance().start(
    context, 
    downloadConfig, 
    (IDownloadListener)(new FlutterManager$downloadDynamicSO$2$1(it)));
  Object var10000 = var5.getOrThrow();
  return var10000;
}

public final class FlutterManager$downloadDynamicSO$2$1 implements IDownloadListener {
  FlutterManager$downloadDynamicSO$2$1(Continuation $captured_local_variable$0) {
    this.$it = $captured_local_variable$0;
  }
  final Continuation $it;

  public void onSuccess(@Nullable String url, @Nullable Uri savePath) {
    super.onSuccess(url, savePath);
    Continuation var3 = this.$it;
    String var4 = savePath != null ? savePath.getPath() : null;
    //下载完成,调用 SafeContinuation#resumeWith()
    var3.resumeWith(Result.constructor-impl(var4));
  }
//...
}
internal actual class SafeContinuation<in T>
  internal actual constructor(
    private val delegate: Continuation<T>,
    initialResult: Any?
      ) : Continuation<T>, CoroutineStackFrame {
    //未携带状态的构造函数,默认状态为 UNDECIDED
    internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)

    // 构造时未赋值状态时,默认初始状态为 UNDECIDED
    @Volatile
    private var result: Any? = initialResult

    public actual override fun resumeWith(result: Result<T>) {
      while (true) {
        //结合挂起小节中的逻辑,此时 result = COROUTINE_SUSPENDED
        val cur = this.result 
        when {
          cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return
          cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)) {
            //执行到此处!!
            // 结合挂起小节中的逻辑,此处调用的是 DispatchedContinuation#resumeWith()
            delegate.resumeWith(result)
            return
          }
          else -> throw IllegalStateException("Already resumed")
        }
      }
    }
  }

逻辑执行到此处,最后调用的是 DispatchedContinuation#resumeWith(),该方法与“创建与启动-resumeCancellableWith”小节中的 DispatchedContinuation#resumeCancellableWith() 实现逻辑基本一致,不再重复解读,最终执行到协程体的 invokeSuspend() 逻辑。

BuildersKt.launch$default(CoroutineScopeKt.MainScope(), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
  Object L$0;
  int label;

  @Nullable
  public final Object invokeSuspend(@NotNull Object $result) {
    label40: {
      // var10 = 挂起状态
      Object var10 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch (this.label) {
        case 0:
          //将 label 标记为 1
          this.label = 1;
          //...
          break;
        //代码再次执行到 invokeSuspend(),此时 label = 1
        case 1:
          //获取到第一个挂起函数返回的结果,并赋值给 var10000
          ResultKt.throwOnFailure($result);
          var10000 = $result;
          break;
        //...
      }
      var16 = (String)var10000;
      //执行后续示例代码中 .let{} 部分的逻辑
      //... 
      this.label = 2;
      //调用下一个挂起函数
      var10000 = var7.downloadDynamicSO(var8, var4, this);
      if (var10000 == var10) {
         return var10;
      }
      return Unit.INSTANCE;
    }
    //...
  }), 3, (Object)null);

回到 invokeSuspend() 逻辑中,由于第一次挂起时将 label 标记为 1,所以此时执行 case 1 的逻辑,将结果赋值给 var1000,break 跳出 switch,继续执行后续逻辑,最后将 label 标记为 2,调用下一个挂起函数。后面的逻辑又回到了函数的挂起与恢复,和上面解析的一致,这里就不重复了。

总结

博文来自青杉

参考内容

Kotlin协程之再次读懂协程工作原理 - 掘金
深入理解Kotlin协程