Kotlin 协程之二:原理剖析
系列文章:
Kotlin 协程之一:基础使用
Kotlin 协程之二:原理剖析
Kotlin 协程之三:Android中的应用
前面文章过后,我们知道了使用协程的基本步骤后,现在就来对照一些简单的demo和源码,逐层分析一下协程的实现原理吧!
1.协程作用域(运行环境)-CoroutineScope
协程作用域,也就是运行环境,顾名思义,会包含一个协程运行所需的各种参数,比如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(自身)进行调用。
2.协程任务执行环境-Dispatcher
首先,协程的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两个派发器。
(1)Dispatcher.Main
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进行任务的调度,确保任务会在主线程执行。
(2)Dispatcher.IO
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()
线程池-CoroutineScheduler
这里我们挑几个小细节看一下和通常的线程池有何不一样。
i.尽量使用当前线程
private fun submitToLocalQueue(task: Task, fair: Boolean): Int {
val worker = currentWorker