Kotlin 协程之二:原理剖析

本文深入探讨了Kotlin协程的原理,包括CoroutineScope、Dispatcher、协程任务(Coroutine)和协程挂起(suspend)。重点分析了Dispatcher.IO和Dispatcher.Main的实现,以及协程如何利用线程池资源。此外,还讲解了协程任务的创建、挂起、异常处理和取消机制,揭示了协程的多层代理模式和状态机模式在执行中的作用。
摘要由CSDN通过智能技术生成

系列文章:
Kotlin 协程之一:基础使用
Kotlin 协程之二:原理剖析
Kotlin 协程之三:Android中的应用

前面文章过后,我们知道了使用协程的基本步骤后,现在就来对照一些简单的demo和源码,逐层分析一下协程的实现原理吧!

1.协程作用域(运行环境)-CoroutineScope

协程作用域,也就是运行环境,顾名思义,会包含一个协程运行所需的各种参数,比如dispatcher派发器、interceptor拦截器、Job任务管理对象等。
在这里插入图片描述

public interface CoroutineScope {
   
    //协程运行环境(上下文,如dispatcher)
    public val coroutineContext: CoroutineContext
}

每个scope需要一个上下文CoroutineContext对象,context主要是由几个对象组成:

  1. CoroutineDispatcher:执行协程任务的派发器,比如Dispatcher.IO、Dispatcher.Main等,事实上,这些dispatcher对象直接实现了CoroutineContext接口,成为一个context。

  2. ContinuationInterceptor:协程的拦截器,协程内部是通过多层代理模式(下面会讲)实现每一层的功能,其中就用到了拦截器实现其代理对象。

  3. 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是怎么支持这种动态的类呢?

  1. 首先kotlin提供了一个工厂类接口,用来创建MainDispatcher

    public interface MainDispatcherFactory {
         
        fun createDispatcher(allFactories: List<MainDispatcherFactory>): MainCoroutineDispatcher
    }
    
  2. 然后再看反编译的源码

    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/文件夹下获取。

  3. 再看编译生成的apk文件的该文件夹内容
    在这里插入图片描述
    所以android的依赖包是通过向该文件注册类名实现的注册类,并且factory类为AndroidDispatcherFactory。

  4. 最后我们再来看下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
  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值