系列文章:
Kotlin 协程之一:基础使用
Kotlin 协程之二:原理剖析
Kotlin 协程之三:Android中的应用
前面文章过后,我们知道了使用协程的基本步骤后,现在就来对照一些简单的demo和源码,逐层分析一下协程的实现原理吧!
协程作用域,也就是运行环境,顾名思义,会包含一个协程运行所需的各种参数,比如dispatcher派发器、interceptor拦截器、Job任务管理对象等。
public interface CoroutineScope {
//协程运行环境(上下文,如dispatcher)
public val coroutineContext: CoroutineContext
}
每个scope需要一个上下文CoroutineContext对象,context主要是由几个对象组成:
CoroutineDispatcher:执行协程任务的派发器,比如Dispatcher.IO、Dispatcher.Main等,事实上,这些dispatcher对象直接实现了CoroutineContext接口,成为一个context。
ContinuationInterceptor:协程的拦截器,协程内部是通过多层代理模式(下面会讲)实现每一层的功能,其中就用到了拦截器实现其代理对象。
Job:Job是协程任务的接口,里面定义了协程的状态字段、cancel方法、attach方法等;JobSupport类实现了Job的接口,并且提供了协程执行操作的接口,是处理一个协程任务的各种调用的集合;事实上每个协程都继承自JobSupport类,这样每个协程就有了被调用、改变状态的能力;每个协程的context是父协程的context以及自己本身的Job组成,所以每个协程是一个独立的Job对象进行运作,而CoroutineScope里的context,作为顶层context,他的Job是一个JobImpl对象。
//创建scope时会新建JobImpl
public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if (context[Job] != null) context else context + Job())
public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)
class JobImpl(parent: Job?) : JobSupport(true)
//每个协程是一个JobSupport,且context为父context+自身的Job
class AbstractCoroutine<in T>() : JobSupport(active){
public final override val context: CoroutineContext = parentContext + this
}
CoroutineScope的实现简单概括:指定CoroutineContext对象创建CoroutineScope,用CoroutineScope启动协程任务,CoroutineContext的interceptor包装协程,然后通过CoroutineContext的dispatcher调度协程,执行时通过协程的Job(自身)进行调用。
首先,协程的dispatcher都继承自CoroutineDispatcher类,并且该类也继承自CoroutineContext,所以Dispatcher本身就是一个CoroutineContext。
public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor)
public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element
public interface Element : CoroutineContext
这里我们主要看一下我们最常使用的Dispatcher.Main和Dispatcher.IO两个派发器。
Dispatcher.Main没有默认实现,依赖于各个平台的实现,如果没有引入android依赖包,则会抛异常提示,那么kotlin是怎么支持这种动态的类呢?
首先kotlin提供了一个工厂类接口,用来创建MainDispatcher
public interface MainDispatcherFactory {
fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher
}
然后再看反编译的源码
public final <S> List<S> loadProviders$kotlinx_coroutines_core(@NotNull Class<S> paramClass, @NotNull ClassLoader paramClassLoader) {
//从apk的META-INF/services/文件夹下那类名
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("META-INF/services/");
stringBuilder.append(paramClass.getName());
Enumeration enumeration = paramClassLoader.getResources(stringBuilder.toString());
ArrayList arrayList = Collections.list(enumeration);
Iterable iterable = (Iterable)arrayList;
Collection collection = (Collection)new ArrayList();
for (URL uRL : iterable) {
FastServiceLoader fastServiceLoader = INSTANCE;
Intrinsics.checkExpressionValueIsNotNull(uRL, "it");
CollectionsKt.addAll(collection, (Iterable)fastServiceLoader.parse(uRL));
}
collection = CollectionsKt.toSet((Iterable)collection);
iterable = (Iterable)collection;
collection = (Collection)new ArrayList(CollectionsKt.collectionSizeOrDefault(iterable, 10));
//将类名解析为实例对象
for (String str : iterable)
collection.add(INSTANCE.getProviderInstance(str, paramClassLoader, paramClass));
return (List)collection;
}
MainDispatcher的factory会从apk的META-INF/services/文件夹下获取。
再看编译生成的apk文件的该文件夹内容
所以android的依赖包是通过向该文件注册类名实现的注册类,并且factory类为AndroidDispatcherFactory。
最后我们再来看下AndroidDispatcherFactory类
internal class AndroidDispatcherFactory : MainDispatcherFactory {
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) = HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")
}
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)
//android中需要向主looper进行提交调度
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || Looper.myLooper() != handler.looper
}
//通过持有主线程looper的handler进行调度
override fun dispatch(context: CoroutineContext, block: Runnable) {
handler.post(block)
}
...
}
很清楚,就是用持有主线程looper的handler进行任务的调度,确保任务会在主线程执行。
internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
val IO = blocking(systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)))
public fun blocking(parallelism: Int = BLOCKING_DEFAULT_PARALLELISM): CoroutineDispatcher {
return LimitingDispatcher(this, parallelism, TaskMode.PROBABLY_BLOCKING)
}
}
Dispatcher.IO是一个LimitingDispatcher实例,他可以控制同时并发任务数,默认为64个,即最多有64个任务同时在运行。
private class LimitingDispatcher(
val dispatcher: ExperimentalCoroutineDispatcher,
val parallelism: Int,
override val taskMode: TaskMode
) : ExecutorCoroutineDispatcher()
而LimitingDispatcher内部真正调度任务的dispatcher是一个ExperimentalCoroutineDispatcher对象。
open class ExperimentalCoroutineDispatcher(
private val corePoolSize: Int,
private val maxPoolSize: Int,
private val idleWorkerKeepAliveNs: Long,
private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
constructor(
corePoolSize: Int = CORE_POOL_SIZE,
maxPoolSize: Int = MAX_POOL_SIZE,
schedulerName: String = DEFAULT_SCHEDULER_NAME
) : this(corePoolSize, maxPoolSize, IDLE_WORKER_KEEP_ALIVE_NS, schedulerName)
private var coroutineScheduler = createScheduler()
override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
try {
coroutineScheduler.dispatch(block)
} catch (e: RejectedExecutionException) {
DefaultExecutor.dispatch(context, block)
}
private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)
}
我们看到,该dispatcher里面的真正的线程池,是CoroutineScheduler对象,而核心线程数和最大线程数,取决于可与CPU的数量。
internal val CORE_POOL_SIZE = systemProp(
"kotlinx.coroutines.scheduler.core.pool.size",
AVAILABLE_PROCESSORS.coerceAtLeast(2), // !!! at least two here
minValue = CoroutineScheduler.MIN_SUPPORTED_POOL_SIZE
)
internal val AVAILABLE_PROCESSORS = Runtime.getRuntime().availableProcessors()
这里我们挑几个小细节看一下和通常的线程池有何不一样。
private fun submitToLocalQueue(task: Task, fair: Boolean): Int {
val worker = currentWorker() ?: return NOT_ADDED
...
worker.localQueue.add(task, globalQueue)
...
}
private fun currentWorker(): Worker? = (Thread.currentThread() as? Worker)?.takeIf { it.scheduler == this }
如果当前线程是Dispatcher.IO开启的工作线程,那么任务优先交由该线程的任务队列,等待处理。
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
...
when (submitToLocalQueue(task, fair)) {
ADDED -> return
NOT_ADDED -> {
//本地队列已满,放入全局队列,所有线程可取
globalQueue.addLast(task)
}
else -> requestCpuWorker() // ask for help
}
}
如果工作线程本地队列无限大,一味的放入本地队列的话,可能会造成单一线程工作,效率极低,于是每个工作线程有固定大小的queue,满了之后,会放到全局queue中,等待任意空闲工作线程执行。
//工作线程Worker类
override fun run() {
while (!isTerminated && state != WorkerState.TERMINATED) {
val task = findTask()
...
}
...
}
private fun findTaskWithCpuPermit(): Task? {
...
//从本地queue获取任务
localQueue.poll()?.let { return it }
//从全局queue获取任务
if (!globalFirst) globalQueue.removeFirstOrNull()?.let { return it }
//抢占其他线程任务
return trySteal()
}
private fun trySteal(): Task? {
...
//随机一个工作线程
if (stealIndex == 0) stealIndex = nextInt(created)
...
val worker = workers[stealIndex]
if (worker !== null && worker !== this) {
//将其queue里的任务放到自己queue中
if (localQueue.trySteal(worker.localQueue, globalQueue)) {
return localQueue.poll()
}
}
return null
}
如果一个工作线程的本地queue和全局queue都没有任务了,但是其他线程的queue还有任务,此时让其空闲,一是没有充分利用线程提升工作效率,而是线程的空闲状态切换需要开销,所以此时会尝试从任一工作线程的queue中取出任务,放入自己的queue中执行。
以上三点的相互配合,可以充分利用线程资源,避免过多线程的使用及开销,也保证了多任务时的工作效率。
了解了scope和dispatcher后,接下来我们就要创建协程任务了,这里就拿最简单的CoroutineScope(Dispatchers.IO).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
}
这里的start参数,是协程的开启方式,比如默认的DEFAULT是直接执行协程任务,还有LAZY模式,直到我们手动调用协程的start()方法才会执行,相当于一个懒加载的功能。
这里的newCoroutineContext()方法,会合并当前scope的context和传入的context生成一个新的context,作为新协程的context,这里需要明确的是:
context上面说过是scope的上下文执行环境,那协程为什么需要context呢?因为每个协程也实现了CoroutineScope,是一个scope,这也是为什么我们可以在协程内部再建协程!
newCoroutineContext()的合并原则是,两者不同的属性保留,相同的属性则替换为传入的context的属性(即覆盖父context),而如果一方是Empty类型的Context,那么就完全取决于另一方,所以通常情况下,我们在一个scope内部创建的协程,context包括其中的dispatcher等都是来自于scope,除非我们想新建一个在其他dispatcher执行的协程,可以传入context,如:
CoroutineScope(Dispatchers.IO).launch {
launch(Dispatchers.Main){ }
}
此时内部的launch协程的context,就是Dispatcher.Main了。
这里的block参数,其实就是我们的协程代码块,这个部分稍后会细说。
有了这些参数,然后我们会创建一个StandaloneCoroutine对象,这就是一个标准协程对象,这里需要解释的是:
协程对象都继承自AbstractCoroutine类,代表一个协程。
协程对象都实现了CoroutineScope接口,是一个scope(上面提到过)。
协程是将异步任务(一大块代码)分片段执行(执行一段,挂起,再执行一段,再挂起),同一个协程会多次被执行,每次执行一段代码,所以协程对象也实现了Continuation接口,作为一个可执行的代码片段。
然后我们接着来看AbstractCoroutine的start方法。
public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
//初始化父子关系
initParentJob()
//调用CoroutineStart,开启协程
start(block, receiver, this)
}
//不同STRAT模式调用不同方法
public enum class CoroutineStart {
public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>) =
when (this) {
CoroutineStart.DEFAULT -> block.startCoroutineCancellable(receiver, completion)
CoroutineStart.ATOMIC -> block.startCoroutine(receiver, completion)
CoroutineStart.UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
CoroutineStart.LAZY -> Unit // will start lazily
}
}
这里我们会看到,最终会根据不同的START模式来启动协程,参数completion就是我们上一步创建的AbstractCoroutine协程对象。
比如LAZY就不启动,而是需要我们后续手动调用start(),这里我们来看DEFAULT模式。
internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
runSafely(completion) {
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellable(Unit)
}
这段代码就是真正启动协程的逻辑,也是上面提到的,协程是通过多层代理模式实现各层功能的,下面我们逐层分析一下,这也是协程实现的重点!
上面创建的AbstractCoroutine是第一层协程对象,他的主要功能是提供协程状态改变的逻辑。
public final override fun resumeWith(result: Result<T>) {
makeCompletingOnce(result.toState(), defaultResumeMode)
}
resumeWith()是恢复协程的重要方法,也是入口,其中调用的逻辑都是在父类JobSupport中实现。
createCoroutineUnintercepted()方法中传入的completion就是第一层的AbstractCoroutine对象。
AbstractCoroutine是处理协程状态的,那协程任务的逻辑代码呢?是交由第二层BaseContinuationImpl处理的。
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
public final override fun resumeWith(result: Result<Any?>) {
...
val completion = completion!!
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
...
completion.resumeWith(outcome)
return
}
protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}
首先,该对象也可以分段执行代码逻辑的,其实现了Continuation接口,resumeWith()就是恢复协程调用的方法,它里面的逻辑,是调用invokeSuspend()方法,这个方法内部就是我们自己写的协程任务的代码,然后有两种处理结果:
如果此次调用返回的是COROUTINE_SUSPENDED,那么直接返回。
如果此次调用结果返回的正常结果或者出现异常,则将结果/异常对象包装为Result,返回调用completion的resumeWith()方法并传入Result。
那么这里就有几个问题了:
我们自己写的代码,怎么会跑到了该类的invokeSuspend()方法里了呢?
这是kotlin的协程,在kotlin编译器的编译源代码时候,自动完成将我们的代码块,转换为一个BaseContinuationImpl子类的。
比如如下代码块:
CoroutineScope(Dispatchers.IO).launch {
log("launch-start")
delay(2000)
log("launch-end")
}
launch代码块,在经过编译器转换后生成的类如下:
final class TestCoroutines$t$1$1$1 extends SuspendLambda implements Function2<CoroutineScope, Continuation<? super Unit>, Object> {
int label = 0;
TestCoroutines$t$1$1$1(Continuation paramContinuation) { super(2, paramContinuation); }
public final Object invoke(Object paramObject1, Object paramObject2) { return ((null)create(paramObject1, (Continuation)paramObject2)).invokeSuspend(Unit.INSTANCE); }
@Nullable
public final Object invokeSuspend(@NotNull Object paramObject) {
Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
default:
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
case 1:
object = (CoroutineScope)this.L$0;
ResultKt.throwOnFailure(paramObject);
TestCoroutines.access$log(TestCoroutines.INSTANCE, "launch-end");
return Unit.INSTANCE;
case 0:
break;
}
ResultKt.throwOnFailure(paramObject);
paramObject = this.p$;
TestCoroutines.access$log(TestCoroutines.INSTANCE, "launch-start");
this.L$0 = paramObject;
this.label = 1;
if (DelayKt.delay(2000L, this) == object)
return object;
TestCoroutines.access$log(TestCoroutines.INSTANCE, "launch-end");
return Unit.INSTANCE;
}
}
于是乎,终于看到了我们写的代码在哪了,该类是编译器生成的,继承了SuspendLambda(其继承了BaseContinuationImpl),也实现了Function2接口(和我们launch方法接受的block参数类型一致),所以说BaseContinuationImpl是第二层协程对象,主要负责通过调用invokeSuspend()执行我们的任务代码。
那么是如何将我们的代码分段执行的呢?通过仔细观察重写的invokeSuspend()方法实现,我们发现了是使用状态机模式来实现的:
int变量label,作为状态变量,初始值为0.
第一次调用invokeSuspend()时,走到case 0,执行了"launch-start"的log输出,然后执行delay(2000)语句,这是个挂起携程的方法,DelayKt.delay(2000L, this)刚开始肯定返回IntrinsicsKt.getCOROUTINE_SUSPENDED(),所以直接return了,这时,这个协程任务这次的调用就结束了,不会阻塞线程,当然不要忘了把状态label改为1,等待下次执行。
等到2秒后,delay协程任务完成了,会自动通知到被挂起的协程,进行resumeWith()恢复,然后执行第二次invokeSuspend(),此时走到case 1,执行了"launch-end"的log输出,然后结束,至此这个协程任务执行完毕。
第二个问题是,协程如何知道要被挂起?其实我们已经说到了,就是在invokeSuspend()方法里,如果遇上类似delay()这种挂起协程的方法,那么会直接return返回COROUTINE_SUSPENDED值,而在BaseContinuationImpl的resumeWith()中,我们看到,如果返回的是这个值,那么直接return此次执行,并不会调用completion的resumeWith(),说明协程没有结束,已被挂起,等待下次执行。
如果协程任务全部结束,或像上面说的那样有了异常,会调用了completion.resumeWith()结束协程,那么这个completion是谁呢?其实就是第一层的AbstractCoroutine协程对象,因为他才是用来改变协程状态的对象。那么他俩是如何关联起来的呢?我们来看start时的createCoroutineUnintercepted()方法。
public static final <R, T> Continuation<Unit> createCoroutineUnintercepted(@NotNull Function2<? super R, ? super Continuation<? super T>, ? extends Object> paramFunction2, R paramR, @NotNull Continuation<? super T> paramContinuation) {
...
if (paramFunction2 instanceof BaseContinuationImpl)
return ((BaseContinuationImpl)paramFunction2).create(paramR, paramContinuation);
...
}
internal abstract class BaseContinuationImpl(
public val completion: Continuation<Any?>?
)
可以看到,其实就是第一层的协程对象,让第二层的协程对象代理了。
我们来看下一步intercepted()方法,这里就简写了。
public static final <T> Continuation<T> intercepted(@NotNull Continuation<? super T> paramContinuation) {
...
ContinuationImpl continuationImpl = (ContinuationImpl)paramContinuation;
return continuationImpl.intercepted();
}
我们的代码块生成的类,其实都是继承了SuspendLambda类,该类又继承了ContinuationImpl类,该类又继承了BaseContinuationImpl类,所以这里的强转没有问题,然后来看下ContinuationImpl的intercepted()方法。
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
方法意思是,使用context里的ContinuationInterceptor属性的interceptContinuation()方法进行拦截,而我们一般使用的Dispatcher.IO或Dispatcher.Main,都是继承了CoroutineDispatcher类,来看一下他的实现。
public final override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation)
直接返回了一个DispatchedContinuation,并传入了第二层协程对象进行代理,这就是我们的第三层协程对象,他的作用顾名思义,就是用来调度协程的。
override fun resumeWith(result: Result<T>) {
val context = continuation.context
val state = result.toState()
if (dispatcher.isDispatchNeeded(context)) {
_state = state
resumeMode = MODE_ATOMIC_DEFAULT
dispatcher.dispatch(context, this)
}...
}
基本的调度逻辑就是使用dispatcher进行调度,dispatcher在上面已经说过了,说白了就是线程池调度任务罢了,而这每个DispatchedContinuation都继承了DispatchedTask,是一个实现了Runnable的对象,可以用于线程的执行。
public final override fun run() {
try {
val delegate = delegate as DispatchedContinuation<T>
//第二层协程对象BaseContinuationImpl
val continuation = delegate.continuation
...
withCoroutineContext(context, delegate.countOrElement) {
val exception = getExceptionalResult(state)
//调用第二层对象的恢复逻辑(正常或异常状态)
if (exception != null) continuation.resumeWithStackTrace(exception)
else continuation.resume(getSuccessfulResult(state))
...
}
}...
}
这也是代理模式的基本使用。最后再来看一下resumeCancellable()方法。
public static final void resumeCancellable(@NotNull Continuation $this$resumeCancellable, Object value) {
if ($this$resumeCancellable instanceof DispatchedContinuation) {
DispatchedContinuation this_$iv = (DispatchedContinuation)$this$resumeCancellable;
$i$f$resumeCancellable = false;
if (this_$iv.dispatcher.isDispatchNeeded(this_$iv.getContext())) {
this_$iv._state = value;
this_$iv.resumeMode = 1;
//直接派发自己就完事了!
this_$iv.dispatcher.dispatch(this_$iv.getContext(), (Runnable)this_$iv);
} else ...
}...
}
直接调用了第三层协程的对象派发逻辑,进行派发。
第一层对象用来处理协程状态。
第二层对象由编译器将我们的代码转换而成,利用状态机实现代码分段执行和协程挂起,并代理第一层对象。
第三层对象负责使用dispatcher进行调度任务,并代理第二层对象。
每次执行时,调用第三层对象的派发逻辑执行任务,每次执行的是第二层对象的代码逻辑方法,执行到协程挂起后,改变状态,并返回,等待下一次的执行,下一次执行重复iv。
协程的基本执行我们清楚了,再来细说一下协程的挂起,刚刚讲到过,协程的所谓挂起,其实就是在某个代码处,直接return协程任务,等待下次被恢复,这样可以避免像Thread.sleep这种方法长时间阻塞线程,那么我们如何挂起一个协程,也就是如何在某个代码处返回COROUTINE_SUSPENDED呢?又是如何在某个时机恢复被挂起的协程呢?我们来看几个实际案例。
delay()方法已经说了好多次了,我们现在来实际分析一下他的实现。
public suspend fun delay(timeMillis: Long) {
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
}
}
public suspend inline fun <T> suspendCancellableCoroutine(
crossinline block: (CancellableContinuation<T>) -> Unit
): T = suspendCoroutineUninterceptedOrReturn { uCont ->
val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
block(cancellable)
cancellable.getResult()
}
首先我们可以看到,调用了suspendCoroutineUninterceptedOrReturn()方法,这个方法是什么用呢?我们看名字也能看出来,这就是用来挂起协程或直接返回结果(协程可能已经运行完毕)的方法。
private static final <T> Object suspendCoroutineUninterceptedOrReturn(Function1<? super Continuation<? super T>, ? extends Object> paramFunction1, Continuation<? super T> paramContinuation) {
return paramFunction1.invoke(CoroutineIntrinsics.normalizeContinuation(paramContinuation));
}
我们看到,直接调用block,并返回block的结果。那block做了什么呢?
将当前协程(uCont)包装为一个新的CancellableContinuationImpl对象,并代理了当前协程(DispatchedContinuation)对象。
在将这个对象回调到delay()的block里,做了什么呢?调用了context的delay队列的scheduleResumeAfterDelay()方法。
override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>) = schedule(DelayedResumeTask(timeMillis, continuation))
private inner class DelayedResumeTask(
timeMillis: Long,
private val cont: CancellableContinuation<Unit>
) : DelayedTask(timeMillis) {
override fun run() {
with(cont) { resumeUndispatched(Unit) }
}
}
schedule就是像一个队列里放入了一个任务,等到指定时间后,就会运行这个任务,运行的逻辑很简单,就是调用传入的cont,即当时被挂起的协程的resume()方法,后面的事就都知道了吧!
此次调用delay()方法,返回的是包装的当前协程的result。
internal fun getResult(): Any? {
if (trySuspend()) return COROUTINE_SUSPENDED
...
return getSuccessfulResult(state)
}
如果协程还没有执行结束,则会返回COROUTINE_SUSPENDED,此时外部协程,就会像上面说的,协程直接return了,被挂起了;而如果协程已经运行完毕,则把结果直接返回了,协程就不会挂起了。
所以说,协程并不是一定会被挂起,这点很重要。
我们再来看一个async式协程的await()方法。我们只看关键
private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptedOrReturn { uCont ->
val cont = AwaitContinuation(uCont.intercepted(), this)
cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler))
cont.getResult()
}
其实和delay几乎是一样的,只有后续的恢复协程的方式不一样。invokeOnCompletion()是将一个ResumeAwaitOnCompletion对象,作为node保存到当前协程,即要被挂起的外部协程中,其中保存了当前外部协程的封装对象AwaitContinuation,用于后续恢复。
当async协程执行完毕,调用其第一层对象AbstractCoroutine的resumeWith()方法时,会调到notifyHandlers方法。
private inline fun <reified T: JobNode<*>> notifyHandlers(list: NodeList, cause: Throwable?) {
list.forEach<T> { node ->
try {
node.invoke(cause)
}...
}
}
该list就是node的list,此时会调用ResumeAwaitOnCompletion。
private class ResumeAwaitOnCompletion<T>(
job: JobSupport,
private val continuation: CancellableContinuationImpl<T>
) : JobNode<JobSupport>(job) {
override fun invoke(cause: Throwable?) {
...
continuation.resume(state.unboxState() as T)
}
}
这个continuation,就是需要被恢复的协程,后面的事,大家也知道了。
delay()和await()其实差不多,这里再说一种withContext(),他的不一样是:在withContext()协程的所有内部协程(包括其子协程)都结束后,才会恢复挂起的外部协程,这是怎么做的呢?
首先,withContext()的创建过程。
public suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): T = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
...
val coroutine = DispatchedCoroutine(newContext, uCont)
coroutine.initParentJob()
block.startCoroutineCancellable(coroutine, coroutine)
coroutine.getResult()
}
和之前说的挂起协程方式一模一样,这里需要注意的是,创建的协程是DispatchedCoroutine类型,持有了待恢复的协程。
当withContext()协程的代码块执行完成,调用resumeWith(),会调用到tryMakeCompletingSlowPath()方法。
private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?, mode: Int): Int {
...
// now wait for children
val child = firstChild(state)
if (child != null && tryWaitForChild(finishing, child, proposedUpdate))
return COMPLETING_WAITING_CHILDREN
// otherwise -- we have not children left (all were already cancelled?)
if (tryFinalizeFinishingState(finishing, proposedUpdate, mode))
return COMPLETING_COMPLETED
...
}
private tailrec fun tryWaitForChild(state: Finishing, child: ChildHandleNode, proposedUpdate: Any?): Boolean {
val handle = child.childJob.invokeOnCompletion(
invokeImmediately = false,
handler = ChildCompletion(this, state, child, proposedUpdate).asHandler
)
if (handle !== NonDisposableHandle) return true // child is not complete and we've started waiting for it
val nextChild = child.nextChild() ?: return false
return tryWaitForChild(state, nextChild, proposedUpdate)
}
我们看到,在结束一个协程时,会判断其有没有子协程还没有完成(子协程与父协程的持有关系在创建协程时就已完成),如果有未完成的子协程,会创建一个ChildCompletion对象并持有withContext的协程,通过invokeOnCompletion()放入子协程的node list中。
当子协程完成时,按照上面说的,会执行notifyHandlers(),通知所有的node。
private class ChildCompletion(...) : JobNode<Job>(child.childJob) {
override fun invoke(cause: Throwable?) {
parent.continueCompleting(state, child, proposedUpdate)
}
}
private fun continueCompleting(state: Finishing, lastChild: ChildHandleNode, proposedUpdate: Any?) {
val waitChild = lastChild.nextChild()
// try wait for next child
if (waitChild != null && tryWaitForChild(state, waitChild, proposedUpdate)) return // waiting for next child
// no more children to wait -- try update state
if (tryFinalizeFinishingState(state, proposedUpdate, MODE_ATOMIC_DEFAULT)) return
}
node里会通知withContext的协程,继续判断是否有子协程未完成,如果都完成了,则执行tryFinalizeFinishingState()方法,最终执行到afterCompletionInternal()方法,而withContext创建的DispatchedCoroutine的该方法为:
override fun afterCompletionInternal(state: Any?, mode: Int) {
if (state is CompletedExceptionally) {
val exception = if (mode == MODE_IGNORE) state.cause else recoverStackTrace(state.cause, uCont)
uCont.resumeUninterceptedWithExceptionMode(exception, mode)
} else {
uCont.resumeUninterceptedMode(state as T, mode)
}
}
直接恢复被挂起的外部协程。
综上所述:
协程状态的最终完成,需要等到内部所有协程完成;但是仅限于状态的完成,不会影响协程任务本身代码块的执行完成。
诸如withContext()、coroutineScope()这类的协程,会挂起外部协程,直到内部所有协程完成才会恢复继续向下执行。
说了这么多,我们自己如何定义协程的挂起呢?需要通过两个kotlin提供的功能来实现:
suspendCoroutine()相关的API,挂起协程,并在block里拿到待恢复的协程,在合适的时机调用resume进行恢复。
suspend关键字,表示该方法是可挂起的方法,约束该方法只能在协程内部或者suspend方法内部调用。
private suspend fun getData() = suspendCoroutine<String> {
it.resume("result")
}
CoroutineScope(Dispatchers.IO).launch { val data = getData() }
下面来看异常部分,如果在invokeSuspend()方法中,我们的代码出现了异常,会怎么办呢?
val completion = completion!!
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
...
completion.resumeWith(outcome)
可以看到,将异常包装到Result中,还是调用resumeWith()方法,最终调用到tryMakeCompletingSlowPath()方法。
private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?, mode: Int): Int {
...
notifyRootCause?.let { notifyCancelling(list, it) }
...
if (tryFinalizeFinishingState(finishing, proposedUpdate, mode))
return COMPLETING_COMPLETED
...
}
//1.cancel掉parent和children
private fun notifyCancelling(list: NodeList, cause: Throwable) {
onCancelling(cause)
notifyHandlers<JobCancellingNode<*>>(list, cause)
cancelParent(cause)
}
//2.尝试处理exception
private fun tryFinalizeFinishingState(state: Finishing, proposedUpdate: Any?, mode: Int): Boolean {
...
if (finalException != null) {
val handled = cancelParent(finalException) || handleJobException(finalException)
if (handled) (finalState as CompletedExceptionally).makeHandled()
}
...
//3.逐层向上通知结束
completeStateFinalization(state, finalState, mode)
return true
}
第一步,会将父协程和子协程cancel掉,第二步自己尝试处理异常,第三步逐层向上通知结束,并传递exception。
这里主要是第二步,cancelParent()方法取消父协程,一般都是返回true的,直到根协程会返回false,此时需要执行handleJobException()方法处理,也就是说只有根协程才会尝试处理异常。
protected open fun handleJobException(exception: Throwable): Boolean = false
private open class StandaloneCoroutine(...) : AbstractCoroutine<Unit>(parentContext, active) {
override fun handleJobException(exception: Throwable): Boolean {
handleCoroutineException(context, exception)
return true
}
}
这里发现个问题,协程基类里直接返回false,而StandaloneCoroutine(launch式协程)才会重写进行处理,难道除了launch协程,比如async协程作为根协程的话,就不处理异常了么?确实是这样的,比如下面这个代码:
CoroutineScope(Dispatchers.IO).async {
launch { throw IllegalStateException("erroe") }
}
最终将没有异常抛出,一切正常。。。是不是很坑。
那StandaloneCoroutine协程是怎么处理的呢?
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
...
context[CoroutineExceptionHandler]?.let {it.handleException(context, exception)}
}
context的CoroutineExceptionHandler是什么呢?和上面说的Dispatcher.Main的来源一样,是根据在apk的META-INF下声明的类名拿到的类实例。
internal class AndroidExceptionPreHandler : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler, Function0<Method?> {
override fun handleException(context: CoroutineContext, exception: Throwable) {
val thread = Thread.currentThread()
if (Build.VERSION.SDK_INT >= 28) {
thread.uncaughtExceptionHandler.uncaughtException(thread, exception)
} else {
(preHandler?.invoke(null) as? Thread.UncaughtExceptionHandler)
?.uncaughtException(thread, exception)
}
}
}
处理很简单,就是交由线程的UncaughtExceptionHandler处理,也就是和正常的处理一直,抛出了异常,停止了进程。这段代码会正常抛出异常:
CoroutineScope(Dispatchers.IO).launch { launch { throw IllegalStateException("error") } }
这里要单独说一下async协程的await方法的异常处理,当async协程代码块内部产生异常:
CoroutineScope(Dispatchers.IO).async {
val job = async { throw IllegalStateException("error") }
try {
val res = job.await()
} catch (e: Exception) {
}
}
在恢复外部协程的时候,会将异常当做参数,即await()的结果传递回来,如果判断是异常类型,则会直接throw出去,所以如果此时我们用try-catch捕获await,是可以捕获到异常的,而await处理异常是编译器生成代码时完成的:
public final Object invokeSuspend(@NotNull Object paramObject) {
case 0:
...
//挂起
if (deferred.await(this) == coroutine_suspended) {
return coroutine_suspended;
}
break;
case 1:
//恢复后如果结果是异常则直接throw,此处result就是paramObject,是await()的结果
if (result instanceof Failure) {
throw ((Failure) result).exception;
}
break;
}
但是这里需要注意的是,如果根协程不是async协程,而是launch协程的话,还是会直接抛出异常的,因为在恢复外部协程之前就已经被launch协程处理了,这点很重要也很坑。。。
总结一下:
协程抛出异常,会取消父协程和子协程。
异常会逐步传递到根协程进行处理,async协程不作处理,launch协程正常抛出异常。
async协程的await方法可以进行捕获异常,但是前提是根协程不能处理异常。
最后,再来看协程的取消,我们使用协程的cancel()(Job接口提供的方法)来取消一个协程。
public override fun cancel(cause: CancellationException?) { cancelInternal(cause) }
我们发现cancel方法可以传入一个CancellationException对象,用于描述取消的情况信息,而这个对象是一个Exception异常对象,接着看里面执行到的makeCancelling()方法。
private fun makeCancelling(cause: Any?): Boolean {
when (state) {
...
is Incomplete -> {
val causeException = causeExceptionCache ?: createCauseException(cause).also { causeExceptionCache = it }
if (state.isActive) {
if (tryMakeCancelling(state, causeException)) return true
}...
}
}
}
private fun tryMakeCancelling(state: Incomplete, rootCause: Throwable): Boolean {
val cancelling = Finishing(list, false, rootCause)
if (!_state.compareAndSet(state, cancelling)) return false
notifyCancelling(list, rootCause)
return true
}
private fun notifyCancelling(list: NodeList, cause: Throwable) {
onCancelling(cause)
notifyHandlers<JobCancellingNode<*>>(list, cause)
cancelParent(cause)
}
首先将协程自身状态改为取消结束状态,然后通知取消父协程和子协程,这逻辑看上去和处理异常一样,但最后当然不是抛出异常的,我们接着看。
//子协程取消
private inline fun <reified T: JobNode<*>> notifyHandlers(list: NodeList, cause: Throwable?) {
node.invoke(cause)
}
override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
public final override fun parentCancelled(parentJob: ParentJob) { cancelImpl(parentJob) }
internal fun cancelImpl(cause: Any?): Boolean { return makeCancelling(cause) }//正常取消
//父协程取消
private fun cancelParent(cause: Throwable): Boolean {
val isCancellation = cause is CancellationException
return parent.childCancelled(cause) || isCancellation//cancel()时返回一定为true,就不会调用handleJobException处理异常了
}
override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause)
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true//cancel()时直接return,不会取消协程
return cancelImpl(cause) && handlesException
}
我们看到,子协程会正常取消掉,而父协程由于异常是CancellationException类型,所以不会处理异常以及取消协程了。
总结一下:
cancel()其实和异常处理逻辑一样,只是异常类型被特殊化处理,避免直接抛出异常和取消父协程了。
取消协程会取消所有子协程,而父协程不会被取消。
这里我们看到,取消协程,只是状态的变化(AbstractCoroutine层对象),而BaseContinuationImpl层的代码块逻辑执行没有影响,所以,取消协程不会取消代码块逻辑的运行,这点非常非常重要,对于代码块逻辑,可以通过协程的isActive状态变量来判断是否应该继续执行。
isActive判断,在kotlin自带的一些API,如delay、async{}.await()里已经实现,这些方法我们不用自己处理。
val job = launch {
while (isActive) {
log("launch")
}
}
delay(1000)
job.cancel()