Kotlin协程实现原理

前言

本篇解析Kotlin/JVM中的协程的实现原理。

初看suspend关键字

下面的例子模拟一个网络请求:

class Temp {
    suspend fun fetchData(argument: String): Boolean {
        val result = netRequest(argument)
        return result == 0
    }

    // 模拟网络请求
    suspend fun netRequest(argument: String): Int {
        delay(1000)
        return argument.length
    }
}

这两个方法都使用了suspend关键字修饰,我们将这个文件的字节码反编译为等同效果的Java代码:

public final class Temp {
   @Nullable
   public final Object fetchData(@NotNull String argument, @NotNull Continuation var2) {
      Object $continuation;
      label25: {
         if (var2 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var2;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label25;
            }
         }

         $continuation = new ContinuationImpl(var2) {
            // $FF: synthetic field
            Object result;
            int label;

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return Temp.this.fetchData((String)null, this);
            }
         };
      }

      Object $result = ((<undefinedtype>)$continuation).result;
      Object var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      Object var10000;
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         ((<undefinedtype>)$continuation).label = 1;
         var10000 = this.netRequest(argument, (Continuation)$continuation);
         if (var10000 == var6) {
            return var6;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         var10000 = $result;
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      int result = ((Number)var10000).intValue();
      return Boxing.boxBoolean(result == 0);
   }

   @Nullable
   public final Object netRequest(@NotNull String argument, @NotNull Continuation var2) {
      Object $continuation;
      label20: {
         if (var2 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var2;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }

         $continuation = new ContinuationImpl(var2) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return Temp.this.netRequest((String)null, this);
            }
         };
      }

      Object $result = ((<undefinedtype>)$continuation).result;
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         ((<undefinedtype>)$continuation).L$0 = argument;
         ((<undefinedtype>)$continuation).label = 1;
         if (DelayKt.delay(1000L, (Continuation)$continuation) == var5) {
            return var5;
         }
         break;
      case 1:
         argument = (String)((<undefinedtype>)$continuation).L$0;
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      return Boxing.boxInt(argument.length());
   }
}

几行协程相关的代码,竟然对应了这么多的Java代码,可见kotlin编译器为我们做了很多事情

上面代码的可读性不高,例如有<undefinedtype>这种未定义的类型,我使用jd-guiTemp.class文件再进行了一次反编译,获取到了更多信息,我将上面的反编译的一大串代码和jd-gui反编译获取的信息进行整合,并且对一些类和变量进行适当的重命名,得出信息更完整且可读性更高的「Temp.class反编译后对应的Java代码」,首先是fetchData相关的:

public final Object fetchData(@NotNull String argument,@NotNull Continuation completion) {
    Object $continuation;
    label25:
    {
        if (completion instanceof FetchDataStateMachine) {
            $continuation = (FetchDataStateMachine) completion;
            if (($continuation.label & Integer.MIN_VALUE) != 0) {
                $continuation.label -= Integer.MIN_VALUE;
                break label25;
            }
        }
        $continuation = new FetchDataStateMachine(completion);
    }

    Object $result = $continuation.result;
    Object resultTemp;
    switch ($continuation.label) {
        case 0:
            ResultKt.throwOnFailure($result);
            $continuation.label = 1;
            resultTemp = this.netRequest(argument, (Continuation) $continuation);
            if (resultTemp == COROUTINE_SUSPENDED) {
                return COROUTINE_SUSPENDED;
            }
            break;
        case 1:
            ResultKt.throwOnFailure($result);
            resultTemp = $result;
            break;
        default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

    int result = ((Number) resultTemp).intValue();
    return Boxing.boxBoolean(result == 0);
}

static final class FetchDataStateMachine extends ContinuationImpl {
    Object result;
    int label;

    FetchDataStateMachine(Continuation $completion) {
        super($completion);
    }

    @Nullable
    public final Object invokeSuspend(@NotNull Object $result) {
        this.result = $result;
        this.label |= Integer.MIN_VALUE;
        return Temp.this.fetchData(null, (Continuation<? super Boolean>) this);
    }
}

netRequest相关的代码,与fetchData相关的代码,在结构和形式上类似:

public final Object netRequest(@NotNull String argument,@NotNull Continuation completion) {
    Object $continuation;
    label20:
    {
        if (completion instanceof NetRequestStateMachine) {
            $continuation = (NetRequestStateMachine) completion;
            if (($continuation.label & Integer.MIN_VALUE) != 0) {
                $continuation.label -= Integer.MIN_VALUE;
                break label20;
            }
        }
        $continuation = new NetRequestStateMachine(completion);
    }

    Object $result = $continuation.result;
    switch ($continuation.label) {
        case 0:
            ResultKt.throwOnFailure($result);
            $continuation.functionParameter = argument;
            $continuation.label = 1;
            if (DelayKt.delay(1000L, (Continuation) $continuation) == COROUTINE_SUSPENDED) {
                return COROUTINE_SUSPENDED;
            }
            break;
        case 1:
            argument = (String) ($continuation.functionParameter);
            ResultKt.throwOnFailure($result);
            break;
        default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

    return Boxing.boxInt(argument.length());
}

static final class NetRequestStateMachine extends ContinuationImpl {
    Object result;
    int label;
    Object functionParameter;

    NetRequestStateMachine(Continuation $completion) {
        super($completion);
    }

    @Nullable
    public final Object invokeSuspend(@NotNull Object $result) {
        this.result = $result;
        this.label |= Integer.MIN_VALUE;
        return Temp.this.netRequest(null, (Continuation<? super Integer>) this);
    }
}

可以发现,反编译后的Java代码中,fetchDatanetRequest方法都多了一个Continuation completion参数,这是Kotlin Compiler帮我们做的,对于suspend修饰的函数,编译的时候Kotlin Compiler会帮我们在该函数中传入一个Continuation参数,使用Continuation参数代替了suspend修饰符,这个参数有什么含义呢?

初识续体

续体是理解协程工作原理的一个关键。

先看传统的网络请求:

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

interface Callback {
    fun success(user: User)
    fun failure(t: Throwable)
}

class Model {
    fun getUserInfo(callback: Callback) {
        Thread.sleep(1000) // 模拟网络请求
        callback.success(User(1, "giagor"))
    }
}

class Business {
    val model = Model()

    fun getUserInfo() {
        model.getUserInfo(object : Callback {
            override fun success(user: User) {
                showMsg(user.toString())
            }

            override fun failure(t: Throwable) {
                showMsg(t.message ?: "")
            }
        })
    }

    fun showMsg(msg: String) {
        // ...
    }
}

在使用Model进行网络请求的时候,使用Callback接收网络请求的结果,我们这时候可以将Callback看作一个续体,即网络请求的续体,用于接收网络请求的结果。

在协程中使用Continuation接口表示一个续体,它代表一个挂起点之后的延续,即 挂起点之后的剩余应执行的代码

public interface Continuation<in T> {
    // 与该续体对应的协程的上下文
    public val context: CoroutineContext
    // 恢复对应协程的执行,并且传递一个表示成功或失败的result作为最后一个挂起点的返回值
    public fun resumeWith(result: Result<T>)
}

Kotlin 1.3,也有可以方便地调用resumeWith的扩展函数:

public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))

正如前面所说,对于suspend修饰的函数,Kotlin Compiler会帮我们在该函数中传入一个Continuation参数,使用Continuation参数代替了suspend修饰符,通过Continuation参数,Kotlin Compiler可以将我们的协程代码转化为等价的回调代码,也就是说,Kt编译器帮我们写好了那些回调的代码,至于怎么帮我们写的后面会分析,这种通过传递Continuation来控制异步调用流程被称作CPS变换(Continuation-Passing-Style Transformation

状态机

fetchData函数编译时会生成下面的一个静态内部类(续体):

static final class FetchDataStateMachine extends ContinuationImpl {
    Object result;
    int label;

    FetchDataStateMachine(Continuation $completion) {
        super($completion);
    }

    @Nullable
    public final Object invokeSuspend(@NotNull Object $result) {
        this.result = $result;
        this.label |= Integer.MIN_VALUE;
        return Temp.this.fetchData(null, (Continuation<? super Boolean>) this);
    }
}

FetchDataStateMachine的继承关系如下:

FetchDataStateMachine -> ContinuationImpl -> BaseContinuationImpl -> Continuation

FetchDataStateMachine接收一个名称为$completionContinuation参数,$completion被保存在父类BaseContinuationImpl中:

internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {...}

通过$completion可以将fetchData函数的执行结果传递回给调用fetchData的函数,有了$completion,才有能力去实现回调

状态机FetchDataStateMachine声明了resultlabel两个变量

  • result表示上一个Continuation的结果,比如有函数AB,函数内部分别声明了ContinuationAContinuationBA调用B并且将ContinuationA传入B中保存。在后续回调的过程中,ContinuationA可以从result变量中拿到ContinuationB::invokeSuspend的执行结果
  • labelKotlin Compiler可以识别函数内部哪个地方会挂起,每一个挂起点(suspension point)被表示为状态机的一个状态(state),这些状态通过switch case语句表示出来。label表示当前应该执行状态机的哪一个状态,具体来说就是要进入哪一个case,通过label变量就记录下了状态机当前的状态

再看下fetchData的前半部分代码:

public final Object fetchData(@NotNull String argument,@NotNull Continuation completion) {
    Object $continuation;
    label25:
    {
        if (completion instanceof FetchDataStateMachine) {
            $continuation = (FetchDataStateMachine) completion;
            if (($continuation.label & Integer.MIN_VALUE) != 0) {
                $continuation.label -= Integer.MIN_VALUE;
                break label25;
            }
        }
        $continuation = new FetchDataStateMachine(completion);
    }
    ...
}

它会判断传入的completion是否为FetchDataStateMachine类型,若是则对它的label变量做些操作,若不是则直接创建一个FetchDataStateMachine并且传入completioncompletion会被保存下来)。

再看下fetchData的后半部分代码:

public final Object fetchData(@NotNull String argument,@NotNull Continuation completion) {
    Object $continuation;
    ...
    Object $result = $continuation.result;
    Object resultTemp;
    switch ($continuation.label) {
        case 0:
            ResultKt.throwOnFailure($result);
            $continuation.label = 1;
            resultTemp = this.netRequest(argument, (Continuation) $continuation);
            if (resultTemp == COROUTINE_SUSPENDED) {
                return COROUTINE_SUSPENDED;
            }
            break;
        case 1:
            ResultKt.throwOnFailure($result);
            resultTemp = $result;
            break;
        default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

    int result = ((Number) resultTemp).intValue();
    return Boxing.boxBoolean(result == 0);
}

fetchData方法原先的代码语句会被划分为switch下的多个case语句,在这里就是

image-20220110000849800

FetchDataStateMachine中的label变量就是控制当前要执行哪个case分支。

可见,函数与续体构成了一个有限状态机(FSM,即 Finite-State Machine),来控制协程代码的执行

何为「非阻塞式挂起」?

netRequest方法中,调用了delay(1000)挂起了当前的协程,简单看下delay方法反编译后的代码:

public static final Object delay(long timeMillis, @NotNull Continuation $completion) {
    if (timeMillis <= 0L) {
        return Unit.INSTANCE;
    } else {
        // 实现类
        CancellableContinuationImpl cancellableContinuationImpl = new CancellableContinuationImpl(IntrinsicsKt.intercepted($completion), 1);
        cancellableContinuationImpl.initCancellability();
        // 向上转型
        CancellableContinuation cont = (CancellableContinuation)cancellableContinuationImpl;
        if (timeMillis < Long.MAX_VALUE) {
            // 延时操作
            getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont);
        }
		// 获取执行结果
        Object result = cancellableContinuationImpl.getResult();
        if (result == COROUTINE_SUSPENDED) {
            DebugProbesKt.probeCoroutineSuspended($completion);
        }
		// 返回结果
        return result;
    }
}

在该方法里会执行延时操作,如果需要挂起,就会返回COROUTINE_SUSPENDED值给调用者。

结合fetchDatanetRequestdelay反编译的代码,我们可以得出下面的这个调用图:

image-20220110170319770

图中红色的线表示函数返回COROUTINE_SUSPENDED,需要挂起。当delay方法需要挂起的时候,它返回COROUTINE_SUSPENDED,接着netRequest方法返回COROUTINE_SUSPENDED,接着fetchData方法返回COROUTINE_SUSPENDED,重复这个过程直到调用栈的最上层。

通过这种「结束方法调用」的方式,让协程暂时不在这个线程上面执行,让线程可以去处理其它的任务(包括执行其它的协程),这也就是为什么协程的挂起不会阻塞当前的线程,这也是「非阻塞式挂起」的由来

如何恢复?

既然协程挂起了,那就有相应的协程的恢复。先说结论:协程恢复的实质是对续体进行回调

暂时还没有研究delay函数的具体实现,但是delay函数会在某个子线程执行等待操作,等延时时间到达之后,就会调用传给delay函数的$completionresumeWith方法,也就是调用NetRequestStateMachineresumeWith方法NetRequestStateMachine的继承关系、父类如下:

NetRequestStateMachine -> ContinuationImpl -> BaseContinuationImpl -> Continuation

BaseContinuationImpl目前是我们分析的一个重点,它主要做了下面的几件事情:

  1. 保存completion:它保存了fetchData方法的FetchDataStateMachine实例,使得可以一级一级地向上回调续体。
  2. 重写resumeWith方法:BaseContinuationImpl重写了Continuation接口的resumeWith方法,该方法用于恢复协程,也是协程恢复的核心逻辑。

我们查看BaseContinuationImpl类的定义:

internal abstract class BaseContinuationImpl(
    public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
    // This implementation is final. This fact is used to unroll resumeWith recursion.
    public final override fun resumeWith(result: Result<Any?>) {
        // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
        var current = this
        var param = result
        while (true) {
            // 在每个恢复的continuation进行调试探测,使得调试库可以精确跟踪挂起的调用栈中哪些部分
            // 已经恢复了。
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

    protected abstract fun invokeSuspend(result: Result<Any?>): Any?

    protected open fun releaseIntercepted() {
        // does nothing here, overridden in ContinuationImpl
    }    
    
    ...
}

重点是resumeWith方法的实现,它在一个while(true)循环下面执行回调的逻辑。我们结合前面给出的fetchDatanetRequest反编译后的代码,看看delay函数的延时时间到达时调用NetRequestStateMachineresumeWith方法,后续的执行流程是怎样的:

  1. 执行NetRequestStateMachine父类BaseContinuationImplresumeWith方法。
  2. 执行当前续体也就是NetRequestStateMachineinvokeSuspend方法(NetRequestStateMachine有实现该方法,忘记了的话可以回头看看之前的反编译代码)。
  3. NetRequestStateMachineinvokeSuspend方法调用了netRequest方法,并且将续体自身作为参数传入。
  4. netRequest方法中,由于completion的类型就是NetRequestStateMachine因此可以直接使用该续体,不用像之前第一次进入netRequest方法那样需要创建一个新的续体。此时续体的label值为1,于是进入netRequestcase 1语句分支。

实际上这个过程有对续体的label进行一些运算转化的操作,但是最终label的值都是1,做的运算转化操作不影响我们的分析,因此并不是重点

  1. 从续体中取出一开始传入netRequest方法的参数,也就是argument,返回argument.length。为了方便后面阐述,这里将该返回值argument.length记为netRequest-Return
  2. 接着netRequest方法结束,NetRequestStateMachine::invokeSuspend方法也执行结束,netRequest-Return也作为invokeSuspend方法的返回值,该返回值会传递到BaseContinuationImplresumeWith方法中,在resumeWith方法中,将netRequest-Return包装为Result保存到outcome变量中。
  3. 判断NetRequestStateMachine持有的completion是否为BaseContinuationImpl类型,我们知道它持有的实例其实就是FetchDataStateMachine,因此肯定是BaseContinuationImpl,于是进行了变量的更新
    // 把current更新为FetchDataStateMachine实例
	current = completion
	// 把param更新为outcome(包装了netRequest-Return的Result)
    param = outcome

通过这种方式,其实就可以实现回调,我们继续往后看。

  1. 继续进行下一轮while循环,在with块中会执行FetchDataStateMachine::invokeSuspend,在invokeSuspend里,将传入的参数param保存到result变量里(其实这和传统的回调类似,传统的回调中也是要将下层的执行结果回调给上层),接着调用了fetchData方法。
  2. fetchData方法中,由于传入的completion已是FetchDataStateMachine类型,因此无需再去创建新的续体。由于此时续体label的值为1,所以会进入case 1语句,并且将netRequest方法的执行结果保存在resultTemp变量中,最终fetchData方法结束并返回结果result == 0,为了方便阐述,将fetchData方法的执行结果记为fetchData-Return
  3. FetchDataStateMachine::invokeSuspend方法也会结束并返回fetchData-Return,然后在BaseContinuationImplresumeWith方法中将fetchData-Return包装为Result。然后会判断FetchDataStateMachine持有的completion是否为BaseContinuationImpl类型。
  4. 代码的后续走向,我们目前是不清楚的,我们得知道在协程中调用fetchData方法的时候会做些什么,才能清楚后续的代码走向

从上面的流程分析中,我们对协程的恢复有了一个基本的认识,下面给出流程图进行总结:

image-20220116115359269

再看看上面续体的调用过程,其实就是层层往上地调用续体的invokeSuspend方法,从过程来看有点像递归调用,但是BaseContinuationImpl::resumeWith的实现却和递归不太一样,它的实现是在while(true)循环中,对续体调用一次invokeSuspend方法,然后记录它的返回结果,将这个返回结果作为下一个续体invokeSuspend的方法参数。

简单来讲,就是在调用一个续体的invokeSuspend方法,待这个方法执行结束后,再调用下一个续体的invokeSuspend方法。这样做的一个原因是避免调用栈过深,在BaseContinuationImpl::resumeWith也有相关的注释说明:

This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume

启动协程

我们在一个协程中去调用fetchData方法:

class Temp2 {
    fun execute() {
        GlobalScope.launch(Dispatchers.Main) {
            Temp().fetchData("argument")
        }
    }
}

通过launch方法可以启动一个协程,其源码如下:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

协程中的代码会被包装为一个block,默认情况下会创建一个StandaloneCoroutine,然后调用它的start方法并返回StandaloneCoroutine

StandaloneCoroutine间接的实现了Job接口和Continuation<T>接口,如下:

private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, active) {
    override fun handleJobException(exception: Throwable): Boolean {
        handleCoroutineException(context, exception)
        return true
    }
}

public abstract class AbstractCoroutine<in T>(
    /**
     * The context of the parent coroutine.
     */
    @JvmField
    protected val parentContext: CoroutineContext,
    active: Boolean = true
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {...}

可以看出StandaloneCoroutine身兼多职,实现了Job, Continuation<T>, CoroutineScope接口。后面代码跟踪可以得出一个结论,最顶层的续体实现是协程自身,也就是协程恢复的时候续体会一层层地往上回调,最顶层的续体就是协程coroutine自身,即StandaloneCoroutine(这里以StandaloneCoroutine为例)

另外还要注意一点,launch方法中传入的 block块类型:

 block: suspend CoroutineScope.() -> Unit

它等价于下面的这种函数类型:

// CoroutineScope:扩展函数转化而来
// Continuation:suspend关键字转化而来,Continuation参数由编译器传入
block : (CoroutineScope,Continuation) -> Unit

// 或者通过Function2的形式表示
block : Function2<CoroutineScope,Continuation,Unit>

接着跟踪下启动协程的调用过程。在launch方法中,调用了AbstractCoroutine::start方法:

    public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
        initParentJob()
        // 语法糖,实际是调用CoroutineStart.invoke方法
        start(block, receiver, this)
    }

CoroutineStart::invoke方法:

    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方法可以知道CoroutineStart的默认值是CoroutineStart.DEFAULT,因此会调用到blockstartCoroutineCancellable方法:

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)
    }

我在AS跟踪createCoroutineUnintercepted的代码调用时,发现会跳转到IntrinsicsKt.class文件,这个文件里面找不到方法的源代码,最后找到了IntrinsicsJvm.kt文件,找到createCoroutineUnintercepted方法源码,如下:

# R:CoroutineScope
# T:Unit
@SinceKotlin("1.3")
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
    receiver: R,
    completion: Continuation<T>
): Continuation<Unit> {
    // probeCoroutineCreated方法直接返回completion
    val probeCompletion = probeCoroutineCreated(completion)
    return if (this is BaseContinuationImpl)
        create(receiver, probeCompletion)
    else {
        createCoroutineFromSuspendFunction(probeCompletion) {
            (this as Function2<R, Continuation<T>, Any?>).invoke(receiver, it)
        }
    }
}

这里会判断this的类型是否为BaseContinuationImplthis就是我们之前在launch中传入的lambda块,那么这个lambda代码块是什么类型的呢?想要知道这个答案,我们得对这一节刚开始给出的代码进行反编译

kotlin代码:

class Temp2 {
    fun execute() {
        GlobalScope.launch(Dispatchers.Main) {
            Temp().fetchData("argument")
        }
    }
}

对反编译后的java代码进行适当的重命名和调整,得出:

public final class Temp2 {
    ...
    static final class LaunchLambda extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {
        int label;

        LaunchLambda(Continuation $completion) {
            super(2, $completion);
        }

        @Nullable
        public final Object invokeSuspend(@NotNull Object $result) {
            switch (this.label) {
                case 0:
                    ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
                    this.label = 1;
                    if ((new Temp()).fetchData("argument", (Continuation<? super Boolean>) this) == COROUTINE_SUSPENDED)
                        return COROUTINE_SUSPENDED;
                    (new Temp()).fetchData("argument", (Continuation<? super Boolean>) this);
                    return Unit.INSTANCE;
                case 1:
                    ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
                    return Unit.INSTANCE;
            }
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
        }

        @NotNull
        public final Continuation<Unit> create(@Nullable Object value, @NotNull Continuation<? super LaunchLambda> $completion) {
            return (Continuation<Unit>) new LaunchLambda($completion);
        }

        @Nullable
        public final Object invoke(@NotNull CoroutineScope p1, @Nullable Continuation<?> p2) {
            return ((LaunchLambda) create(p1, p2)).invokeSuspend(Unit.INSTANCE);
        }
    }
}

可以看出在Temp2里面会自动生成一个静态内部类LaunchLambda,它对应着launch方法中传入的lambda块。LaunchLambda的继承关系(由上到下,子类到父类的顺序):

LaunchLambda
-> SuspendLambda // 用suspend修饰的lambda块都会继承至这个类
-> ContinuationImpl
-> BaseContinuationImpl // 重写了resumeWith函数
-> Continuation    

OK,回到createCoroutineUnintercepted方法中,现在可以回答刚刚提出的问题了,lambda传入的lambda块是不是BaseContinuationImpl类型呢?根据上面的继承关系得出,当然是!那么它就会调用LaunchLambdacreate方法,注意第二个参数传入的是completion(代码中写的是probeCompletion),它最终会被保存在父类BaseContinuationImplcompletion变量中,这个completion参数就是launch方法中创建的StandaloneCoroutine,即协程本身,它作为协程恢复时的最顶层续体

通过调用create方法获取到一个LaunchLambda实例,createCoroutineUnintercepted方法执行结束并返回LaunchLambda实例,接着代码执行又回到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)
    }

这里有两部分调用,先是调用intercepted方法,然后再调用resumeCancellableWith方法。intercepted方法与续体拦截机制有关,后面会介绍,这里先忽略,这里直接认为调用了LaunchLambda实例的resumeCancellableWith方法即可,该方法如下:

public fun <T> Continuation<T>.resumeCancellableWith(
    result: Result<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
    is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
    else -> resumeWith(result)
}

那么会走到resumeWith方法,前面提到过该方法在父类BaseContinuationImpl实现,在该方法里面会调用invokeSuspend方法,invokeSuspend方法在LaunchLambda中实现了,如下:

@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
    switch (this.label) {
        case 0:
            ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
            this.label = 1;
            if ((new Temp()).fetchData("argument", (Continuation<? super Boolean>) this) == COROUTINE_SUSPENDED)
                return COROUTINE_SUSPENDED;
            (new Temp()).fetchData("argument", (Continuation<? super Boolean>) this);
            return Unit.INSTANCE;
        case 1:
            ResultKt.throwOnFailure(SYNTHETIC_LOCAL_VARIABLE_1);
            return Unit.INSTANCE;
    }
    throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}

一开始label的值为0,所以会进入case 0语句分支,在该语句分支里面,会设置label的值为1,然后创建一个Temp对象并且调用它的fetchData方法,并把LaunchLambda自身作为参数传入,也就是LaunchLambda实例会被保存在fetchData方法创建的续体的completion变量里,方便协程恢复的时候进行回调。

现在续体的持有图:

image-20220115105300664

到了这里,从启动一个协程到协程最终是如何挂起的,我们已经可以串联起来了。在「如何恢复?」一节中,协程恢复的最后几个步骤我们还没有分析,这里把它分析完,然后整个协程恢复的流程也可以串起来了。

协程恢复的后续流程:

  1. FetchDataStateMachine::invokeSuspend执行完后,会在BaseContinuationImplresumeWith方法中判断FetchDataStateMachine所持有的completion(即LaunchLambda)是否为BaseContinuationImpl类型,由LaunchLambda的继承关系,容易得出答案为「是」,所以会进入下一轮while循环,调用LaunchLambdainvokeSuspend方法。
  2. 由于label = 1所以会进入case 1语句,里面直接return Unit。接着判断LaunchLambda持有的completion(即StandaloneCoroutine)是否为BaseContinuationImpl类型,根据StandaloneCoroutine的继承关系容易得出答案为「不是」,所以会调用StandaloneCoroutineresumeWith方法。
  3. StandaloneCoroutineresumeWith方法在父类AbstractCoroutine中实现:
    public final override fun resumeWith(result: Result<T>) {
        val state = makeCompletingOnce(result.toState())
        // 如果在等子协程完成,则返回
        if (state === COMPLETING_WAITING_CHILDREN) return
        // 应该是做一些后续处理
        afterResume(state)
    }

此时最顶层的续体(协程自身)也恢复了。

  1. BaseContinuationImpl::resumeWith方法执行结束,整个协程的恢复也完成了。

在之前流程图的基础上进行补充完善:

一、协程至上而下调用的流程图(协程挂起)

image-20220115113715867

其中蓝色的文本和线条表示新增的,红色的文本和线条表示挂起的过程。

二、协程至下而上恢复的流程图(协程恢复)

image-20220116164836532

其中蓝色的文本和线条表示新增的,橙色的文本和线条表示方法调用的结束。

协程上下文

协程上下文CoroutineContext定义了协程的行为,它记录了当前协程所持有的信息,是协程运行中一个重要的数据对象。CoroutineContext是一个接口:

public interface CoroutineContext {...}

在续体中就有CoroutineContext的相关信息:

public interface Continuation<in T> {
    // 与该续体对应的协程的上下文
    public val context: CoroutineContext
    // 恢复对应协程的执行,并且传递一个表示成功或失败的result作为最后一个挂起点的返回值
    public fun resumeWith(result: Result<T>)
}

下面几种元素都是「协程上下文」的元素:

  • Job:控制协程的生命周期。
  • CoroutineDispatcher:将工作分派到适当的线程。
  • CoroutineName:协程的名称,可用于调试。
  • CoroutineExceptionHandler:处理未捕获的异常。

CoroutineContext可以看做是CoroutineContext.Element的一个集合,集合中的每个元素都可以使用CoroutineContext.Key进行定位,且每个元素的Key都是不同的。

CoroutineContext.Element的定义:

    public interface Element : CoroutineContext {...}

可以看到Element本身也实现了CoroutineContext接口,这很奇怪,看上去好像是Int实现了List<Int>接口一样,为什么元素本身也是集合了呢?其实这主要是为了方便API的设计,这样的话,一个元素比如Job也可以直接作为一个CoroutineContext,而不需要创建一个只包含一个元素的List,多个元素之间也可以通过「+」进行拼接,如:

scope.launch(CoroutineName("coroutine") + Dispatchers.Main) {...}

这里的「+」其实是操作符重载,对应CoroutineContext声明的plus方法:

    public operator fun plus(context: CoroutineContext): CoroutineContext = ...

「协程上下文」存储元素的方式比较巧妙,它内部并不是创建一个集合,集合的每个位置都存放一个元素。它借助了一个CombinedContext结构来实现数据的存取,CombinedContext的定义及get方法:

internal class CombinedContext(
    private val left: CoroutineContext,
    private val element: Element
) : CoroutineContext, Serializable {
    override fun <E : Element> get(key: Key<E>): E? {
        var cur = this
        while (true) {
            cur.element[key]?.let { return it }
            val next = cur.left
            if (next is CombinedContext) {
                cur = next
            } else {
                return next[key]
            }
        }
    }
    ...
}

从构造函数中可以看出它包含两部分内容:leftelement。也就是说一个CombinedContext内部可能包含多个元素。

  • left:可能是普通的上下文元素(CoroutineContext.Element),也可能又是一个CombinedContext(又包含多个上下文元素)。
  • element:一个协程上下文元素。

CombinedContextget方法中,有一个while(true)循环,执行过程如下:

  1. 它会先判断当前element元素与传入的key是否相符,是的话直接返回该元素,否则获取到left部分。
  2. leftCombinedContext部分,则对left变量重复步骤1。
  3. left不是CombinedContext部分,则直接调用它的get方法获取元素(获取不到则返回null)。

另外,也可以看出element先于left被访问,所以越靠右边的上下文元素,其优先级越高

Key用于标识协程上下文元素,看看它的定义:

public interface CoroutineContext {
	...
    public interface Key<E : Element>

    public interface Element : CoroutineContext {
        // 用于标识元素的Key
    	public val key: Key<*>
        ...
    }
}

CoroutineContext.Element有个抽象类实现,可以让我们更方便地实现上下文元素:

public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element

CoroutineName为例,分析如何实现一个协程上下文元素:

public data class CoroutineName(
    val name: String
    /* CoroutineName.Key可以简写为CoroutineName */
) : AbstractCoroutineContextElement(CoroutineName) {
    
    public companion object Key : CoroutineContext.Key<CoroutineName>
	...
}

首先声明一点,传入父类AbstractCoroutineContextElement的参数是CoroutineName.Key,只是它可以简写为CoroutineName。其实这也很好理解,在Kotlin中,我们调用伴生对象方法的时候,是可以省去伴生对象的类名的,这里也是同样的道理。

CoroutineName内部声明了一个继承至CoroutineContext.Key的伴生对象Key,并将其作为构造参数传入父类AbstractCoroutineContextElement中,以此作为该协程上下文元素的Key

上面是实现协程上下文元素的一种普遍做法,即在协程上下文元素里面定义一个伴生对象,以伴生对象为Key,标识该上下文元素

最后再看一下CoroutineContext的完整定义:

public interface CoroutineContext {
    // 根据key获取元素
    public operator fun <E : Element> get(key: Key<E>): E?

    // 翻译为"折叠",它与上下文元素的累加有关
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    // 协程上下文元素的累加
    public operator fun plus(context: CoroutineContext): CoroutineContext = ...
        
	// 当前CoroutineContext中,去掉key标识的元素后,剩下的上下文元素(以CoroutineContext形式返回)
    public fun minusKey(key: Key<*>): CoroutineContext

    public interface Key<E : Element>

    public interface Element : CoroutineContext {
        // 标识上下文元素的Key
        public val key: Key<*>

        // key相同则返回元素自身,否则返回null
        public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        // 执行传入的operation函数
        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}

CoroutineContextplus方法:

public operator fun plus(context: CoroutineContext): CoroutineContext =
    if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
        context.fold(this) { acc, element ->
            val removed = acc.minusKey(element.key)
            if (removed === EmptyCoroutineContext) element else {
                // make sure interceptor is always last in the context (and thus is fast to get when present)
                val interceptor = removed[ContinuationInterceptor]
                if (interceptor == null) CombinedContext(removed, element) else {
                    val left = removed.minusKey(ContinuationInterceptor)
                    if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                        CombinedContext(CombinedContext(left, element), interceptor)
                }
            }
        }

为了方便后面阐述,记调用形式为A + B,假设A是含有多个元素的协程上下文,B是单个上下文元素。该方法的大致执行流程如下:

  1. 若元素B是空的,则返回原来的上下文A。
  2. 在fold的lambda块中,可以认为acc为A,element为B。
  3. 若A中减去element.key元素后(记为C),C为空上下文,则返回B(相当于元素B替换了上下文A)。
  4. 查看C中是否有ContinuationInterceptor元素,没有则将C和B拼接后返回。
  5. C中剔除ContinuationInterceptor,记为D,若D是空的,则将B和ContinuationInterceptor拼接然后返回。
  6. D不是空的,则将D和B和ContinuationInterceptor拼接然后返回。

简单来说,这里就是要将「传入的协程上下文元素」与「原来的协程上下文元素」进行拼接,若传入的元素与原来集合中的元素的key有冲突,则用传入的元素替换掉原来集合中key冲突的元素。在上下文元素拼接的时候,若有ContinuationInterceptor元素则要确保它在「协程上下文元素集合」的最右边,这样它的优先级最高,从协程上下文获取该元素的时候可以更快地获取到(至于为什么元素在右边,元素的优先级就高、获取快,在前面介绍CombinedContext中已经说明过了)。

plus方法的执行流程很难用文字叙述清楚,如果想要知道它的实现流程,可以代入几个例子试试。但是它具体的执行流程并不是要分析的重点,有个大概的印象即可。

续体拦截机制

这里算是协程实现原理解析的最后一环了。我们在使用协程的时候,会使用到一些调度器如Dispatchers.MainDispatchers.IO等调度器来调度线程,在前面的分析中并没有提到协程是如何进行线程调度的。

线程的调度与续体拦截器ContinuationInterceptor有关,它也是一种「协程上下文元素」:

public interface ContinuationInterceptor : CoroutineContext.Element {
    // 续体拦截器对应的Key
	companion object Key : CoroutineContext.Key<ContinuationInterceptor>
    
    // 返回一个续体,该续体对原始的续体进行包装(原始的续体作为方法参数传入)。
    // 如果该方法不想拦截传入的续体,也可以直接返回原来的续体。
    // 当原始续体完成时,如果该续体之前被拦截了,协程框架会调用releaseInterceptedContinuation
    // 方法,传入的参数就是「续体的包装类」。
    public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
    
    // 该函数只有在interceptContinuation成功拦截的情况下,才会被调用。
    // 若原始续体成功被拦截,当原始续体完成且不再被使用时,该方法会被调用,传入的参数是「续体的包装类」。
    public fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        /* do nothing by default */
    }
    ...
}

续体拦截器可以用于拦截一个续体,最常见的续体拦截器就是协程调度器CoroutineDispatcher,可以通过单例类Dispatchers获取到相应的协程调度器。查看CoroutineDispatcher的实现:

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    
    @ExperimentalStdlibApi
    public companion object Key : AbstractCoroutineContextKey<ContinuationInterceptor, CoroutineDispatcher>(
        ContinuationInterceptor,
        { it as? CoroutineDispatcher })    
    
    public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
        
    public abstract fun dispatch(context: CoroutineContext, block: Runnable)
        
    public open fun dispatchYield(context: CoroutineContext, block: Runnable): Unit = dispatch(context, block)
        
    public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        DispatchedContinuation(this, continuation)   
        
    @InternalCoroutinesApi
    public override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
        (continuation as DispatchedContinuation<*>).reusableCancellableContinuation?.detachChild()
    }
        
	...      
}
  • 拦截器:CoroutineDispatcher继承至ContinuationInterceptor,所以它也是一种续体拦截器。
  • 上下文元素的标识:CoroutineDispatcher继承至AbstractCoroutineContextElement,并传入ContinuationInterceptor.Key构造参数,以此来标识自身。
  • isDispatchNeeded:若需要使用dispatch方法进行调度则返回true,否则返回false。该方法默认返回true。协程调度器可以重写该方法,提供一个性能优化以避免不必要的dispatch,例如主线程调度器Dispatchers.Main会判断当前协程是否已经在UI线程中,如果是的话该方法就会返回false,没有必要再去执行dispatch方法进行不必要的线程调度。
  • dispatch:在给定的上下文和线程中,去执行block块。

假设使用的协程调度器是主线程调度器Dispatchers.Main

    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher

查看MainDispatcherLoader.dispatcher

    @JvmField
    val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()

    private fun loadMainDispatcher(): MainCoroutineDispatcher {
        return try {
            val factories = if (FAST_SERVICE_LOADER_ENABLED) {
                FastServiceLoader.loadMainDispatcherFactory()
            } else {
                // We are explicitly using the
                // `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()`
                // form of the ServiceLoader call to enable R8 optimization when compiled on Android.
                ServiceLoader.load(
                        MainDispatcherFactory::class.java,
                        MainDispatcherFactory::class.java.classLoader
                ).iterator().asSequence().toList()
            }
            @Suppress("ConstantConditionIf")
            factories.maxBy { it.loadPriority }?.tryCreateDispatcher(factories)
                ?: createMissingDispatcher()
        } catch (e: Throwable) {
            // Service loader can throw an exception as well
            createMissingDispatcher(e)
        }
    }

调用了tryCreateDispatcher

public fun MainDispatcherFactory.tryCreateDispatcher(factories: List<MainDispatcherFactory>): MainCoroutineDispatcher =
    try {
        createDispatcher(factories)
    } catch (cause: Throwable) {
        createMissingDispatcher(cause, hintOnError())
    }

继续跟踪,发现createDispatcherMainDispatcherFactory接口的一个方法,其中的一个实现在AndroidDispatcherFactory中:

internal class AndroidDispatcherFactory : MainDispatcherFactory {

    override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
        HandlerContext(Looper.getMainLooper().asHandler(async = true))
	...
}

HandlerContext其实就是调度器Dispatchers.Main的最终实现:

# handler:主线程的Handler
internal class HandlerContext private constructor(
    private val handler: Handler,
    private val name: String?,
    private val invokeImmediately: Boolean
) : HandlerDispatcher(), Delay {
	
    public constructor(
        handler: Handler,
        name: String? = null
    ) : this(handler, name, false)
    ...
    override fun isDispatchNeeded(context: CoroutineContext): Boolean {
        return !invokeImmediately || Looper.myLooper() != handler.looper
    }
    
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        handler.post(block)
    }    
    ...
}
  • isDispatchNeeded:通过looper判断协程当前是否在主线程上,是的话返回false,表示不需要再进行线程调度,否则返回true表示需要进行线程调度。

  • dispatch:使用主线程的handler对传入的block块进行post操作。

对「续体拦截器」「协程调度器」有了一定的了解之后,我们再回过头看一下协程调度器是如何发挥作用的。我们前面分析过Cancellable文件的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方法中返回了LaunchLambda实例,在之前的分析中,我们忽略了intercepted方法,直接分析为LaunchLambda会调用resumeCancellableWith方法,若没有为协程设定续体拦截器,那么确实是LaunchLambda会直接调用到resumeCancellableWith方法。我们看看,如果为协程设定了续体拦截器,会发生什么?

查看LaunchLambda调用的intercepted方法,它在IntrinsicsJVM文件中:

public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this

LaunchLambdaContinuationImpl类型,因此会调用到父类ContinuationImpl::intercepted

internal abstract class ContinuationImpl(
    completion: Continuation<Any?>?,
    private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
    constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)
	...
    @Transient
    private var intercepted: Continuation<Any?>? = null    
    
    public fun intercepted(): Continuation<Any?> =
        intercepted
            ?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
                .also { intercepted = it }
    ...
}

刚开始interceptednull,所以会判断协程上下文中是否有ContinuationInterceptor元素,若没有则会返回this(即LaunchLambda自身,并将intercepted变量设置为LaunchLambda),有的话则会调用interceptContinuation方法,假设使用的续体拦截器是Dispatchers.Main,那么就是调用到CoroutineDispatcherinterceptContinuation方法,该方法会返回一个DispatchedContinuation(并将DispatchedContinuation设置到intercepted变量中)。

查看CoroutineDispatcher::interceptContinuation

    public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        DispatchedContinuation(this, continuation)

DispatchedContinuation类:

internal class DispatchedContinuation<in T>(
    @JvmField val dispatcher: CoroutineDispatcher,
    @JvmField val continuation: Continuation<T>
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {...}

在这里的例子中,dispatcher就是Dispatchers.Maincontinuation就是LaunchLambda

再回到Cancellable文件的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)
    }

在有续体拦截器(Dispatchers.Main)的情况下,intercepted方法会返回DispatchedContinuation,接着调用它的resumeCancellableWith方法:

public fun <T> Continuation<T>.resumeCancellableWith(
    result: Result<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
): Unit = when (this) {
    is DispatchedContinuation -> resumeCancellableWith(result, onCancellation)
    else -> resumeWith(result)
}

调用到另外一个resumeCancellableWith方法,这个方法就是在DispatchedContinuation中实现的了:

    inline fun resumeCancellableWith(
        result: Result<T>,
        noinline onCancellation: ((cause: Throwable) -> Unit)?
    ) {
        val state = result.toState(onCancellation)
        if (dispatcher.isDispatchNeeded(context)) { // 需要线程调度
            _state = state
            resumeMode = MODE_CANCELLABLE
            // 线程调度,将自身以Runnable块形式传入
            dispatcher.dispatch(context, this)
        } else { // 不需要线程调度
            executeUnconfined(state, MODE_CANCELLABLE) {
                if (!resumeCancelled(state)) {
                    // 最终会调用continuation.resumeWith,即LaunchLambda.resumeWith
                    resumeUndispatchedWith(result)
                }
            }
        }
    }

可以看到,它调用了dispatcher.isDispatchNeeded来判断是否需要进行线程调度,以Dispatchers.Main为例,就是判断当前协程是否在主线程中运行,是的话则不需要调度,否则需要将协程调度到主线程中运行。

  • 不需线程调度:最终会调用到LaunchLambda.resumeWith,它后续的执行流程之前已经分析过了。
  • 需要线程调度:(以主线程的协程调度器为例)最终会将传入的Runnable在主线程中执行。

Runnablerun方法在哪实现的呢?在DispatchedContinuation的父类DispatchedTask中有run方法的实现:

    public final override fun run() {
        ...
        try {
            // 获取到的delegate其实就是DispatchedContinuation
            val delegate = delegate as DispatchedContinuation<T>
            // 获取到的continuation其实就是LaunchLambda
            val continuation = delegate.continuation
            val context = continuation.context
            val state = takeState() // NOTE: Must take state in any case, even if cancelled
            withCoroutineContext(context, delegate.countOrElement) {
                val exception = getExceptionalResult(state)
                /*
                 * Check whether continuation was originally resumed with an exception.
                 * If so, it dominates cancellation, otherwise the original exception
                 * will be silently lost.
                 */
                val job = if (exception == null && resumeMode.isCancellableMode) context[Job] else null
                if (job != null && !job.isActive) {
                    val cause = job.getCancellationException()
                    cancelCompletedResult(state, cause)
                    continuation.resumeWithStackTrace(cause)
                } else {
                    if (exception != null) {
                        continuation.resumeWithException(exception)
                    } else {
                        // 正常情况下,会执行到这里,调用LaunchLambda的resume方法
                        continuation.resume(getSuccessfulResult(state))
                    }
                }
            }
        } catch (e: Throwable) {
            ...
        } finally {
            ...
        }
    }

run方法中,最终会调用到LaunchLambdaresume方法(内部又会调用到resumeWith方法)。所以这里做的线程调度,其实就是通过主线程的handler,将代码post到主线程中去运行,从而完成线程的调度工作。

另外,还有几个未研究的地方与自己的猜想:

一、releaseIntercepted方法:在BaseContinuationImpl::resumeWith中,每执行完一个续体的invokeSuspend方法,就会调用该续体的releaseIntercepted方法

    protected override fun releaseIntercepted() {
        val intercepted = intercepted
        // intercepted不为null且不为自身(即之前成功拦截续体),就进入If块
        if (intercepted != null && intercepted !== this) {
            // 调用续体拦截器的releaseInterceptedContinuation方法,并传入续体包装类
 		 	context[ContinuationInterceptor]!!.releaseInterceptedContinuation(
            	 intercepted)
        }
        // 将intercepted变量设置为CompletedContinuation
        this.intercepted = CompletedContinuation // just in case
    }

续体拦截器的releaseInterceptedContinuation方法应该是做一些资源清理的工作。

二、像withContext这样的函数:

scope.launch(Dispatchers.Main) {
    withContext(Dispatchers.IO) {}
}
public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T {...}

block块执行完后,会将线程自动切回「启动协程时的协程调度器所指定」的线程,那么它是如何切回来的呢?个人猜测,在协程至上而下调用的时候,协程上下文会一层一层地向下传递,withContextblock块执行的时候,协程上下文会被保存在某个地方,等到block块执行结束的时候,会从之前保存的协程上下文中取出协程调度器,将剩余的代码(协程恢复)调度到相应的线程中去执行,从而实现了 block块执行完后,线程会自动切回「启动协程时的协程调度器所指定」的线程。

参考

  1. 协程咖啡厅 - 构造魔法 - 探索 Kotlin 协程实现原理 - M.D

  2. Suspend functions - Kotlin Vocabulary - YouTube

posted @ 2022-01-19 19:47  Giagor  阅读(1744)  评论(0编辑  收藏  举报