揭秘kotlin協(xié)程中的CoroutineContext

前言

從kotlin1.1開(kāi)始,協(xié)程就被添加到kotlin中作為實(shí)驗(yàn)性功能,直到kotlin1.3,協(xié)程在kotlin中的api已經(jīng)基本穩(wěn)定下來(lái)了,現(xiàn)在kotlin已經(jīng)發(fā)布到了1.4,為協(xié)程添加了更多的功能并進(jìn)一步完善了它,所以我們現(xiàn)在在kotlin代碼中可以放心的引入kotlin協(xié)程并使用它,其實(shí)協(xié)程并不是kotlin獨(dú)有的功能,它是一個(gè)廣泛的概念,協(xié)作式多任務(wù)的實(shí)現(xiàn),除了kotlin外,很多語(yǔ)言如Go、Python等都通過(guò)自己的方式實(shí)現(xiàn)了協(xié)程,本文閱讀前希望你已經(jīng)知道如何使用kotlin協(xié)程,如果不熟悉可以閱讀一下官方文檔:

kotlin coroutines guide

Coroutine的簡(jiǎn)單理解

提到協(xié)程,很對(duì)人會(huì)把它和線程進(jìn)行比較,就像提到線程,很多人會(huì)把它和進(jìn)程進(jìn)行比較,線程和進(jìn)程分別是操作系統(tǒng)中的CPU調(diào)度單位和資源劃分單位,它們?cè)诓僮飨到y(tǒng)中有專(zhuān)門(mén)的數(shù)據(jù)結(jié)構(gòu)代表,而協(xié)程在操作系統(tǒng)中沒(méi)有專(zhuān)門(mén)的數(shù)據(jù)結(jié)構(gòu)代表,所以協(xié)程并不是由操作系統(tǒng)創(chuàng)建和調(diào)度,它而是由程序自己創(chuàng)建和調(diào)度,由于不需要操作系統(tǒng)調(diào)度,所以協(xié)程比線程更加的輕量,切換協(xié)程比切換線程的開(kāi)銷(xiāo)更小,即它的上下文切換比線程更快,因?yàn)椴僮飨到y(tǒng)切換線程時(shí)一般都會(huì)涉及到用戶(hù)態(tài)內(nèi)核態(tài)的轉(zhuǎn)換,這是一個(gè)開(kāi)銷(xiāo)相對(duì)較大的操作。

協(xié)程的實(shí)現(xiàn)依賴(lài)于線程,它不能脫離線程而存在,因?yàn)榫€程才是CPU調(diào)度的基本單位,協(xié)程通過(guò)程序的調(diào)度可以執(zhí)行在一個(gè)或多個(gè)線程之中,所以協(xié)程需要運(yùn)行于線程之中,由于協(xié)程是由程序自己調(diào)度的,所以程序就需要實(shí)現(xiàn)調(diào)度邏輯,不同語(yǔ)言的調(diào)度的實(shí)現(xiàn)不一樣,在kotlin中,通過(guò)Dispatcher來(lái)調(diào)度協(xié)程,而Dispatcher它通常是一個(gè)線程池的實(shí)現(xiàn)或者基于特定平臺(tái)(例如Android)主線程的實(shí)現(xiàn),通過(guò)調(diào)度讓協(xié)程運(yùn)行于一個(gè)或多個(gè)線程之中,這些協(xié)程可以在同一線程的不同時(shí)刻被執(zhí)行,也可以在不同線程上的不同時(shí)刻被執(zhí)行。

協(xié)程可以說(shuō)是編程語(yǔ)言的能力, 是上層的能力,它并不需要操作系統(tǒng)和硬件的支持, 是編程語(yǔ)言為了讓開(kāi)發(fā)者更容易寫(xiě)出協(xié)作式任務(wù)的代碼,而封裝的一種任務(wù)調(diào)度能力,所以協(xié)程通常是包含一段特定邏輯的代碼塊,多個(gè)協(xié)程之間就組合成一段具有特定邏輯的代碼流程,這些編程語(yǔ)言為了讓開(kāi)發(fā)者更方便的使用協(xié)程,它通常會(huì)提供一些關(guān)鍵字, 而這些關(guān)鍵字會(huì)通過(guò)編譯器自動(dòng)生成了一些支持型代碼,例如kotlin中的suspend關(guān)鍵字,對(duì)于suspend修飾的方法,編譯器會(huì)方法生成一些額外的代碼。

上面就是我對(duì)協(xié)程的簡(jiǎn)單理解,總的來(lái)說(shuō):協(xié)程需要線程的承載運(yùn)行,協(xié)程需要程序自己完成調(diào)度,協(xié)程讓你更容易寫(xiě)出協(xié)作式任務(wù)。

Coroutine的簡(jiǎn)單使用

fun main(){
    val scope = CoroutineScope(CoroutineName("Coroutine-Name") + Dispatchers.IO)
    val job = scope.launch(start = CoroutineStart.DEFAULT){
        println("hello world")
    }
    //進(jìn)程保活1s,只有進(jìn)程存活的前提下,協(xié)程才能會(huì)啟動(dòng)和執(zhí)行
    Thread.sleep(1000)
}

上面首先構(gòu)造了一個(gè)CoroutineScope,它是協(xié)程的作用域,用于控制協(xié)程的生命周期,構(gòu)造CoroutineScope需要一個(gè)CoroutineContext,它是協(xié)程的上下文,用于提供協(xié)程啟動(dòng)和運(yùn)行時(shí)需要的信息,這是我們后面需要重點(diǎn)介紹的,最后通過(guò)CoroutineScope的launch方法啟動(dòng)協(xié)程并輸出hello world,其中啟動(dòng)協(xié)程時(shí)可以通過(guò)CoroutineStart指定協(xié)程的啟動(dòng)模式,它是一個(gè)枚舉值,默認(rèn)是立即啟動(dòng),也通過(guò)指定CoroutineStart.LAZY變?yōu)檠舆t啟動(dòng),延遲啟動(dòng)需要你主動(dòng)調(diào)用返回的Job對(duì)象的start方法后協(xié)程才會(huì)啟動(dòng),如果我們想取消掉這個(gè)協(xié)程的執(zhí)行就可以調(diào)用CoroutineScope的cancel方法,或者調(diào)用launch方法返回的Job對(duì)象的cancel方法,其實(shí)CoroutineScope的cancel方法內(nèi)部也是調(diào)用返回的Job對(duì)象的cancel方法來(lái)結(jié)束這個(gè)協(xié)程。

上面就是啟動(dòng)一個(gè)協(xié)程的簡(jiǎn)單步驟,需要用到CoroutineScope、CoroutineContext、CoroutineStart。

通過(guò)自定義CoroutineScope,可以在應(yīng)用程序的某一個(gè)層次開(kāi)啟或者控制協(xié)程的生命周期,例如Android,在ViewModel和Lifecycle類(lèi)的生命周期里提供了CoroutineScope,分別是ViewModelScope和LifecycleScope.

CoroutineContext的元素

構(gòu)造CoroutineScope使用到的CoroutineContext是一個(gè)特殊的集合,這個(gè)集合它既有Map的特點(diǎn),也有Set的特點(diǎn),集合的每一個(gè)元素都是Element,每個(gè)Element都有一個(gè)Key與之對(duì)應(yīng),對(duì)于相同Key的Element是不可以重復(fù)存在的,Element之間可以通過(guò) + 號(hào)組合起來(lái),后面我會(huì)詳細(xì)介紹CoroutineContext這個(gè)特殊集合的結(jié)構(gòu),接下來(lái)我先簡(jiǎn)單講解一下組成CoroutineContext的各個(gè)Element的作用,CoroutineContext主要由以下4個(gè)Element組成:

  • Job:協(xié)程的唯一標(biāo)識(shí),用來(lái)控制協(xié)程的生命周期(new、active、completing、completed、cancelling、cancelled);
  • CoroutineDispatcher:指定協(xié)程運(yùn)行的線程(IO、Default、Main、Unconfined);
  • CoroutineName: 指定協(xié)程的名稱(chēng),默認(rèn)為coroutine;
  • CoroutineExceptionHandler: 指定協(xié)程的異常處理器,用來(lái)處理未捕獲的異常.

它們之間的關(guān)系如下:

image

下面分別介紹一下4個(gè)Element各自的作用:

1、Job

public interface Job : CoroutineContext.Element {
   
    public companion object Key : CoroutineContext.Key<Job> {
        init {
            CoroutineExceptionHandler
        }
    }
  
    public val isActive: Boolean

    public val isCompleted: Boolean

    public val isCancelled: Boolean

    public fun start(): Boolean

    public fun cancel(cause: CancellationException? = null)
  
    public suspend fun join()

    public val children: Sequence<Job>

    //...
}

通過(guò)CoroutineScope的擴(kuò)展方法launch啟動(dòng)一個(gè)協(xié)程后,它會(huì)返回一個(gè)Job對(duì)象,它是協(xié)程的唯一標(biāo)識(shí),這個(gè)Job對(duì)象包含了這個(gè)協(xié)程任務(wù)的一系列狀態(tài),如下:

image

當(dāng)一個(gè)協(xié)程創(chuàng)建后它就處于新建(New)狀態(tài),當(dāng)調(diào)用Job的start/join方法后協(xié)程就處于活躍(Active)狀態(tài),這是運(yùn)行狀態(tài),協(xié)程運(yùn)行出錯(cuò)或者調(diào)用Job的cancel方法都會(huì)將當(dāng)前協(xié)程置為取消中(Cancelling)狀態(tài), 處于取消中狀態(tài)的協(xié)程會(huì)等所有子協(xié)程都完成后才進(jìn)入取消 (Cancelled)狀態(tài),當(dāng)協(xié)程執(zhí)行完成后或者調(diào)用CompletableJob(CompletableJob是Job的一個(gè)子接口)的complete方法都會(huì)讓當(dāng)前協(xié)程進(jìn)入完成中(Completing)狀態(tài), 處于完成中狀態(tài)的協(xié)程會(huì)等所有子協(xié)程都完成后才進(jìn)入完成(Completed)狀態(tài)。

雖然協(xié)程有New、Cancelling、Completing狀態(tài),但是外部是無(wú)法感知這三個(gè)狀態(tài)的,Job只提供了isActive、isCancelled、isCompleted屬性來(lái)供外部判斷協(xié)程是否處于Active、Cancelled、Completed狀態(tài),當(dāng)協(xié)程處于Active狀態(tài)時(shí),isActive為true,isCancelled和isCompleted為false,當(dāng)協(xié)程處于Cancelled狀態(tài)時(shí),isCancelled和isCompleted為true,isActive為false,當(dāng)協(xié)程處于Completed狀態(tài)時(shí),isCompleted為true,isActive和isCancelled為false。

協(xié)程中有兩種類(lèi)型的Job,如果我們平時(shí)啟動(dòng)協(xié)程時(shí)沒(méi)有特意地通過(guò)CoroutineContext指定一個(gè)Job,那么使用launch/async方法啟動(dòng)協(xié)程時(shí)返回的Job它會(huì)產(chǎn)生異常傳播,我們知道協(xié)程有一個(gè)父子的概念,例如啟動(dòng)一個(gè)協(xié)程1,在協(xié)程中繼續(xù)啟動(dòng)協(xié)程2、協(xié)程3,那么協(xié)程1就是協(xié)程2、協(xié)程3的父協(xié)程,協(xié)程2、協(xié)程3就是協(xié)程1的子協(xié)程,每個(gè)協(xié)程都會(huì)有一個(gè)對(duì)應(yīng)的Job,協(xié)程之間的父子關(guān)系是通過(guò)Job對(duì)象維持的,像一顆樹(shù)一樣:

image

所以異常傳播就是這個(gè)Job因?yàn)槌薈ancellationException以外的異常而失敗時(shí),那么父Job就會(huì)感知到并拋出異常,在拋出異常之前,父Job會(huì)取消所有子Job的運(yùn)行,這也是結(jié)構(gòu)化編程的一個(gè)特點(diǎn),如果要抑制這種異常傳播的行為,那么可以用到另外一種類(lèi)型的Job - SupervisorJob,SupervisorJob它不是一個(gè)類(lèi),它是一個(gè)構(gòu)造方法:

public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

SupervisorJob方法會(huì)返回CompletableJob的一個(gè)supervisor實(shí)現(xiàn),CompletableJob是Job的一個(gè)子接口,它比Job接口多了一個(gè)complete方法,這意味著它可以調(diào)用complete方法讓協(xié)程任務(wù)進(jìn)入完成狀態(tài),supervisor實(shí)現(xiàn)的意思是這個(gè)Job它不會(huì)產(chǎn)生異常傳播,每個(gè)Job可以單獨(dú)被管理,當(dāng)SupervisorJob因?yàn)槌薈ancellationException以外的異常而失敗時(shí),并不會(huì)影響到父Job和其他子Job,下面是SupervisorJob的一個(gè)使用例子:

fun main(){
     val parentJob = GlobalScope.launch {
       //childJob是一個(gè)SupervisorJob
        val childJob = launch(SupervisorJob()){
            throw NullPointerException()
        }
        childJob.join()
        println("parent complete")
    }
    Thread.sleep(1000)
}

childJob拋出異常并不會(huì)影響parentJob的運(yùn)行,parentJob會(huì)繼續(xù)運(yùn)行并輸出parent complete。

2、CoroutineDispatcher

public abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
  
  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)
  
  //...
}

CoroutineDispatcher可以指定協(xié)程的運(yùn)行線程,CoroutineDispatcher里面有一個(gè)dispatch方法,這個(gè)dispatch方法用于把協(xié)程任務(wù)分派到特定線程運(yùn)行,kotlin已經(jīng)內(nèi)置了CoroutineDispatcher的4個(gè)實(shí)現(xiàn),可以通過(guò)Dispatchers的Default、IO、Main、Unconfined字段分別返回使用,如下:

public actual object Dispatchers {
    @JvmStatic
    public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
  
     @JvmStatic
    public val IO: CoroutineDispatcher = DefaultScheduler.IO

    @JvmStatic
    public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
  
    @JvmStatic
    public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
}

2.1、Default、IO

Dispatchers.Default和Dispatchers.IO內(nèi)部都是線程池實(shí)現(xiàn),它們的含義是把協(xié)程運(yùn)行在共享的線程池中,我們先看Dispatchers.Default的實(shí)現(xiàn),看createDefaultDispatcher方法:

internal actual fun createDefaultDispatcher(): CoroutineDispatcher = if (useCoroutinesScheduler) DefaultScheduler else CommonPool

DefaultScheduler和CommonPool都是CoroutineDispatcher的子類(lèi),不同的是DefaultScheduler內(nèi)部依賴(lài)的是kotlin自己實(shí)現(xiàn)的線程池邏輯,而CommonPool內(nèi)部依賴(lài)的是java類(lèi)庫(kù)中的Executor,默認(rèn)情況下useCoroutinesScheduler為true,所以createDefaultDispatcher方法返回的是DefaultScheduler實(shí)例,我們看一下這個(gè)DefaultScheduler:

internal object DefaultScheduler : ExperimentalCoroutineDispatcher() {
    val IO: CoroutineDispatcher = LimitingDispatcher(
        this,//DefaultScheduler實(shí)例被傳進(jìn)了LimitingDispatcher中
        systemProp(IO_PARALLELISM_PROPERTY_NAME, 64.coerceAtLeast(AVAILABLE_PROCESSORS)),
        "Dispatchers.IO",
        TASK_PROBABLY_BLOCKING
    )
  
    //...
}

DefaultScheduler中的IO字段就是Dispatchers.IO,它是LimitingDispatcher實(shí)例,所以Dispatchers.IO的實(shí)現(xiàn)是LimitingDispatcher,同時(shí)我們要注意到DefaultScheduler是用object字段修飾,這說(shuō)明它是一個(gè)單例,并且DefaultScheduler實(shí)例被傳進(jìn)了LimitingDispatcher的構(gòu)造方法中,所以LimitingDispatcher就會(huì)持有DefaultScheduler實(shí)例,而DefaultScheduler它的主要實(shí)現(xiàn)都在它的父類(lèi)ExperimentalCoroutineDispatcher中:

@InternalCoroutinesApi
public open class ExperimentalCoroutineDispatcher(
    private val corePoolSize: Int,
    private val maxPoolSize: Int,
    private val idleWorkerKeepAliveNs: Long,
    private val schedulerName: String = "CoroutineScheduler"
) : ExecutorCoroutineDispatcher() {
  
    public 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()
  
   //返回CoroutineScheduler實(shí)例
    private fun createScheduler() = CoroutineScheduler(corePoolSize, maxPoolSize, idleWorkerKeepAliveNs, schedulerName)

    override fun dispatch(context: CoroutineContext, block: Runnable): Unit =
        try {
           //dispatch方法委托給了CoroutineScheduler的dispatch方法
            coroutineScheduler.dispatch(block)
        } catch (e: RejectedExecutionException) {
            //...
        }
  
  internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
        try {
           //dispatchWithContext方法委托給了CoroutineScheduler的dispatch方法
            coroutineScheduler.dispatch(block, context, tailDispatch)
        } catch (e: RejectedExecutionException) {
          //...
        }
    }
  //...
}

我們?cè)倏碊ispatchers.IO對(duì)應(yīng)的LimitingDispatcher實(shí)現(xiàn):

private class LimitingDispatcher(
    private val dispatcher: ExperimentalCoroutineDispatcher,//外部傳進(jìn)的DefaultScheduler實(shí)例
    private val parallelism: Int,
    private val name: String?,
    override val taskMode: Int
) : ExecutorCoroutineDispatcher(), TaskContext, Executor {

    private val queue = ConcurrentLinkedQueue<Runnable>()
    private val inFlightTasks = atomic(0)

    override fun dispatch(context: CoroutineContext, block: Runnable) = dispatch(block, false)

    private fun dispatch(block: Runnable, tailDispatch: Boolean) {
        var taskToSchedule = block
        while (true) {
            val inFlight = inFlightTasks.incrementAndGet()

          if (inFlight <= parallelism) {
                //LimitingDispatcher的dispatch方法委托給了DefaultScheduler的dispatchWithContext方法
                dispatcher.dispatchWithContext(taskToSchedule, this, tailDispatch)
                return
            }

            queue.add(taskToSchedule)

            if (inFlightTasks.decrementAndGet() >= parallelism) {
                return
            }

            taskToSchedule = queue.poll() ?: return
        }
    }
  
  //...
}

從上面分析得知,Dispatchers.Default的實(shí)現(xiàn)是DefaultScheduler,Dispatchers.IO的實(shí)現(xiàn)是LimitingDispatcher,而LimitingDispatcher持有DefaultScheduler實(shí)例,把dispatch操作委托給DefaultScheduler,DefaultScheduler內(nèi)部持有CoroutineScheduler實(shí)例,把dispatch操作委托給CoroutineScheduler,而DefaultScheduler又是一個(gè)單例,所以Dispatchers.Default和Dispatchers.IO它們共用同一個(gè)CoroutineScheduler實(shí)例,它們之間的關(guān)系如下:

image

CoroutineScheduler就是kotlin自己實(shí)現(xiàn)的共享線程池,是Dispatchers.Default和Dispatchers.IO內(nèi)部的共同實(shí)現(xiàn),Dispatchers.Default和Dispatchers.IO共享CoroutineScheduler中的線程,DefaultScheduler和LimitingDispatcher的主要作用是對(duì)CoroutineScheduler進(jìn)行線程數(shù)、任務(wù)數(shù)等配置,CoroutineScheduler使用工作竊取算法(Work Stealing)重新實(shí)現(xiàn)了一套線程池的任務(wù)調(diào)度邏輯,它的性能、擴(kuò)展性對(duì)協(xié)程的任務(wù)調(diào)度更友好,具體的邏輯可以查看這個(gè)類(lèi)的dispatch方法:

internal class CoroutineScheduler(
    @JvmField val corePoolSize: Int,
    @JvmField val maxPoolSize: Int,
    @JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,
    @JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME
) : Executor, Closeable {
  
      override fun execute(command: Runnable) = dispatch(command)
  
      fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
        val task = createTask(block, taskContext)
        //...
        if (task.mode == TASK_NON_BLOCKING) {
            //...
        } else {
            //...
        }
      }

        //...
}

所以這個(gè)線程池既可以運(yùn)行兩種類(lèi)型的任務(wù):CPU密集型任務(wù)和IO密集型任務(wù),用一個(gè)mode來(lái)區(qū)別,當(dāng)你為協(xié)程指定Dispatchers.Default時(shí),Dispatcher會(huì)把協(xié)程的任務(wù)指定為CPU密集型任務(wù),對(duì)應(yīng)mode為TASK_NON_BLOCKING,當(dāng)你為協(xié)程指定Dispatchers.IO時(shí),Dispatcher會(huì)把協(xié)程的任務(wù)指定為IO密集型任務(wù),對(duì)應(yīng)mode為TASK_PROBABLY_BLOCKING,所以這時(shí)CoroutineScheduler就可以根據(jù)task mode作出不同的線程創(chuàng)建、調(diào)度、喚醒策略,當(dāng)啟動(dòng)協(xié)程時(shí)沒(méi)有指定Dispatcher,默認(rèn)會(huì)使用Dispatchers.Default。

當(dāng)運(yùn)行CPU密集型任務(wù)時(shí),CoroutineScheduler最多有corePoolSize個(gè)線程被創(chuàng)建,corePoolSize它的取值為max(2, CPU核心數(shù)),即它會(huì)盡量的等于CPU核心數(shù),當(dāng)運(yùn)行IO密集型任務(wù)時(shí),它可以創(chuàng)建比corePoolSize更多的線程來(lái)運(yùn)行IO型任務(wù),但不能大于maxPoolSize,maxPoolSize會(huì)取一個(gè)很大的值,默認(rèn)為max(corePoolSize, min(CPU核心數(shù) * 128, 2^21 - 2)),即大于corePoolSize,小于2^21 - 2,而2^21 - 2是一個(gè)很大的數(shù)約為2M,但是CoroutineScheduler是不可能創(chuàng)建這么多線程的,所以就需要外部限制提交的任務(wù)數(shù),而Dispatchers.IO構(gòu)造時(shí)就通過(guò)LimitingDispatcher默認(rèn)限制了最大線程并發(fā)數(shù)parallelism為max(64, CPU核心數(shù)),即Dispatchers.IO最多只能提交parallelism個(gè)任務(wù)到CoroutineScheduler中執(zhí)行,剩余的任務(wù)被放進(jìn)一個(gè)隊(duì)列中等待。

CPU密集型任務(wù):CPU密集型任務(wù)的特點(diǎn)是執(zhí)行任務(wù)時(shí)CPU會(huì)處于忙碌狀態(tài),任務(wù)會(huì)消耗大量的CPU資源,例如計(jì)算復(fù)雜的算術(shù)、視頻解碼等,如果此時(shí)線程數(shù)太多,超過(guò)了CPU核心數(shù),那么這些超出來(lái)的線程是得不到CPU的執(zhí)行的,只會(huì)浪費(fèi)內(nèi)存資源,因?yàn)榫€程本身也有棧等空間,同時(shí)線程過(guò)多,頻繁的線程切換帶來(lái)的消耗也會(huì)影響線程池的性能,所以對(duì)于CPU密集型任務(wù),線程池并發(fā)線程數(shù)等于CPU核心數(shù)才能讓CPU的執(zhí)行效率最大化;

IO密集型任務(wù):IO密集型任務(wù)的特點(diǎn)是執(zhí)行任務(wù)時(shí)CPU會(huì)處于閑置狀態(tài),任務(wù)不會(huì)消耗大量的CPU資源,例如網(wǎng)絡(luò)請(qǐng)求、IO操作等,線程執(zhí)行IO密集型任務(wù)時(shí)大多數(shù)處于阻塞狀態(tài),處于阻塞狀態(tài)的線程是不占用CPU的執(zhí)行時(shí)間,這時(shí)CPU就處于閑置狀態(tài),為了讓CPU忙起來(lái),執(zhí)行IO密集型任務(wù)時(shí)理應(yīng)讓線程的創(chuàng)建數(shù)量更多一點(diǎn),理想情況下線程數(shù)應(yīng)該等于提交的任務(wù)數(shù),對(duì)于這些多創(chuàng)建出來(lái)的線程,當(dāng)它們閑置時(shí),線程池一般會(huì)有一個(gè)超時(shí)回收策略,所以大部分情況下并不會(huì)占用大量的內(nèi)存資源,但也會(huì)有極端情況,所以對(duì)于IO密集型任務(wù),線程池并發(fā)線程數(shù)應(yīng)盡可能地多才能提高CPU的吞吐量,這個(gè)盡可能地多的程度并不是無(wú)限大,而是根據(jù)業(yè)務(wù)情況設(shè)定,但肯定要大于CPU核心數(shù)。

2.2、Unconfined

Dispatchers.Unconfined的含義是不給協(xié)程指定運(yùn)行的線程,在第一次被掛起(suspend)之前,由啟動(dòng)協(xié)程的線程執(zhí)行它,但被掛起后, 會(huì)由恢復(fù)協(xié)程的線程繼續(xù)執(zhí)行, 如果一個(gè)協(xié)程會(huì)被掛起多次, 那么每次被恢復(fù)后, 都有可能被不同線程繼續(xù)執(zhí)行,看下面的一個(gè)例子:

fun main(){
    GlobalScope.launch(Dispatchers.Unconfined){
        println(Thread.currentThread().name)

        //掛起
        withContext(Dispatchers.IO){
            println(Thread.currentThread().name)
        }
        //恢復(fù)
        println(Thread.currentThread().name)

        //掛起
        withContext(Dispatchers.Default){
            println(Thread.currentThread().name)
        }
        //恢復(fù)
        println(Thread.currentThread().name)
    }

    //進(jìn)程保活
    Thread.sleep(1000)
}

運(yùn)行輸出:
main
DefaultDispatcher-worker-1
DefaultDispatcher-worker-1
DefaultDispatcher-worker-3
DefaultDispatcher-worker-3

協(xié)程啟動(dòng)時(shí)指定了Dispatchers.Unconfined,所以第一次執(zhí)行時(shí)是由啟動(dòng)協(xié)程的線程執(zhí)行,上面在主線程中啟動(dòng)了協(xié)程,所以第一次輸出主線程main,withContext方法是一個(gè)suspend方法,它可以?huà)炱甬?dāng)前協(xié)程,并把指定的代碼塊運(yùn)行到給定的上下文中,直到代碼塊運(yùn)行完成并返回結(jié)果,第一個(gè)代碼塊通過(guò)withContext方法把它運(yùn)行在Dispatchers.IO中,所以第二次輸出了線程池中的某一個(gè)線程DefaultDispatcher-worker-1,第一個(gè)代碼塊執(zhí)行完畢后,協(xié)程在DefaultDispatcher-worker-1線程中恢復(fù),所以協(xié)程恢復(fù)后執(zhí)行在DefaultDispatcher-worker-1線程中,所以第三次繼續(xù)輸出DefaultDispatcher-worker-1,第二個(gè)代碼塊同理。

那么Dispatchers.Unconfined是怎么做到的呢,我們看下Unconfined對(duì)應(yīng)的CoroutineDispatcher實(shí)現(xiàn) - kotlinx.coroutines.Unconfined:

internal object Unconfined : CoroutineDispatcher() {
  
    override fun isDispatchNeeded(context: CoroutineContext): Boolean = false

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        // It can only be called by the "yield" function. See also code of "yield" function.
        val yieldContext = context[YieldContext]
        if (yieldContext != null) {
            // report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
            yieldContext.dispatcherWasUnconfined = true
            return
        }
        throw UnsupportedOperationException("Dispatchers.Unconfined.dispatch function can only be used by the yield function. " +
            "If you wrap Unconfined dispatcher in your code, make sure you properly delegate " +
            "isDispatchNeeded and dispatch calls.")
    }
}

Unconfined他重寫(xiě)了CoroutineDispatcher的isDispatchNeeded方法和dispatch方法,isDispatchNeeded方法返回了false,表示不需要dispatch,而默認(rèn)CoroutineDispatcher的isDispatchNeeded方法是返回true的,Dispatchers.Default和Dispatchers.IO都沒(méi)有重寫(xiě)這個(gè)方法,Unconfined的dispatch方法沒(méi)有任何任務(wù)調(diào)度的邏輯,只是寫(xiě)明了只有當(dāng)調(diào)用yield方法時(shí),Unconfined的dispatch方法才會(huì)被調(diào)用,yield方法是一個(gè)suspend方法,當(dāng)在協(xié)程中調(diào)用這個(gè)方法時(shí)表示當(dāng)前協(xié)程讓出自己所在的線程給其他協(xié)程運(yùn)行,所以正常情況下是不會(huì)調(diào)用Unconfined的dispatch方法的。

在kotlin中每個(gè)協(xié)程都有一個(gè)Continuation實(shí)例與之對(duì)應(yīng),當(dāng)協(xié)程恢復(fù)時(shí)會(huì)調(diào)用Continuation的resumeWith方法,它的實(shí)現(xiàn)在DispatchedContinuation中,如下:

internal class DispatchedContinuation<in T>(
    @JvmField val dispatcher: CoroutineDispatcher,//協(xié)程的的CoroutineDispatcher實(shí)例
    @JvmField val continuation: Continuation<T>//代表協(xié)程的Continuation實(shí)例
) : DispatchedTask<T>(MODE_UNINITIALIZED), CoroutineStackFrame, Continuation<T> by continuation {
  
  //...
  
  override fun resumeWith(result: Result<T>) {
        val context = continuation.context
        val state = result.toState()
        if (dispatcher.isDispatchNeeded(context)) {
            _state = state
            resumeMode = MODE_ATOMIC
            dispatcher.dispatch(context, this)
        } else {//Unconfined走這里的邏輯
           //調(diào)用executeUnconfined方法
            executeUnconfined(state, MODE_ATOMIC) {
                withCoroutineContext(this.context, countOrElement) {
                   //調(diào)用Continuation的resumeWith方法
                    continuation.resumeWith(result)
                }
            }
        }
    }
}

我們注意到by關(guān)鍵字,這是kotlin中的委托實(shí)現(xiàn),DispatchedContinuation通過(guò)類(lèi)委托加強(qiáng)了Continuation的resumeWith方法,即在調(diào)用Continuation的resumeWith方法之前增加了一些自己的邏輯,我們可以看到DispatchedContinuation的resumeWith方法中會(huì)根據(jù)CoroutineDispatcher的isDispatchNeeded方法返回值做出不同處理,當(dāng)isDispatchNeeded方法返回true時(shí),會(huì)調(diào)用協(xié)程的CoroutineDispatcher的dispatch方法,而當(dāng)isDispatchNeeded方法返回false時(shí),不會(huì)調(diào)用CoroutineDispatcher的dispatch方法而是調(diào)用executeUnconfined方法,上面講到Unconfined的isDispatchNeeded方法返回了false,我們看executeUnconfined方法:

private inline fun DispatchedContinuation<*>.executeUnconfined(
    contState: Any?,
    mode: Int, 
    doYield: Boolean = false,
    block: () -> Unit
): Boolean {
    assert { mode != MODE_UNINITIALIZED }
   //從ThreadLocal中取出EventLoop
    val eventLoop = ThreadLocalEventLoop.eventLoop
    if (doYield && eventLoop.isUnconfinedQueueEmpty) return false
   //判斷是否在執(zhí)行Unconfined任務(wù)
    return if (eventLoop.isUnconfinedLoopActive) {
        _state = contState
        resumeMode = mode
        //調(diào)用EventLoop的dispatchUnconfined方法把Unconfined任務(wù)放進(jìn)EventLoop中
        eventLoop.dispatchUnconfined(this)
        true 
    } else {
        //執(zhí)行Unconfined任務(wù)
        runUnconfinedEventLoop(eventLoop, block = block)
        false
    }
}

internal inline fun DispatchedTask<*>.runUnconfinedEventLoop(
    eventLoop: EventLoop,
    block: () -> Unit
) {
    eventLoop.incrementUseCount(unconfined = true)
    try {
        //先執(zhí)行block代碼塊,block()就是executeUnconfined方法傳進(jìn)的代碼塊, block()里面會(huì)調(diào)用Continuation的resumeWith方法
        block()
        while (true) {
           //再調(diào)用EventLoop的processUnconfinedEvent方法執(zhí)行EventLoop中的Unconfined任務(wù),直到EventLoop中的所有Unconfined任務(wù)執(zhí)行完才跳出循環(huán)
            if (!eventLoop.processUnconfinedEvent()) break
        }
    } catch (e: Throwable) {
       //...
    } finally {
        eventLoop.decrementUseCount(unconfined = true)
    }
}

可以看到對(duì)于Unconfined任務(wù),是在當(dāng)前線程馬上執(zhí)行或者通過(guò)當(dāng)前線程的EventLoop來(lái)執(zhí)行的,EventLoop是存放在ThreadLocal中的,所以EventLoop它是跟當(dāng)前線程相關(guān)聯(lián)的,而EventLoop也是CoroutineDispatcher的一個(gè)子類(lèi):

internal abstract class EventLoop : CoroutineDispatcher() {
  
  //...
  
  private var unconfinedQueue: ArrayQueue<DispatchedTask<*>>? = null
  
  public fun dispatchUnconfined(task: DispatchedTask<*>) {
        val queue = unconfinedQueue ?: ArrayQueue<DispatchedTask<*>>().also { unconfinedQueue = it }
        queue.addLast(task)
  }
  
  public fun processUnconfinedEvent(): Boolean {
        val queue = unconfinedQueue ?: return false
        val task = queue.removeFirstOrNull() ?: return false
        task.run()
        return true
  }
}

EventLoop中有一個(gè)雙端隊(duì)列用于存放Unconfined任務(wù),Unconfined任務(wù)是指指定了Dispatchers.Unconfined的協(xié)程任務(wù),EventLoop的dispatchUnconfined方法用于把Unconfined任務(wù)放進(jìn)隊(duì)列的尾部,processUnconfinedEvent方法用于從隊(duì)列的頭部移出Unconfined任務(wù)執(zhí)行,所以executeUnconfined方法里面的策略就是:在當(dāng)前線程立即執(zhí)行Unconfined任務(wù),如果當(dāng)前線程已經(jīng)在執(zhí)行Unconfined任務(wù),就暫時(shí)把它放進(jìn)跟當(dāng)前線程關(guān)聯(lián)的EventLoop中,等待執(zhí)行,同時(shí)Unconfined任務(wù)里面會(huì)調(diào)用Continuation的resumeWith方法恢復(fù)協(xié)程運(yùn)行,這也是為什么指定了Dispatchers.Unconfined后協(xié)程恢復(fù)能夠被恢復(fù)協(xié)程的線程執(zhí)行的原因。

2.3、Main

Dispatchers.Main的含義是把協(xié)程運(yùn)行在平臺(tái)相關(guān)的只能操作UI對(duì)象的Main線程,所以它根據(jù)不同的平臺(tái)有不同的實(shí)現(xiàn),kotlin它支持下面三種平臺(tái):

  • kotlin/js:kotlin/js是kotlin對(duì)JavaScript的支持,提供了轉(zhuǎn)換kotlin代碼,kotlin標(biāo)準(zhǔn)庫(kù)的能力,npm包管理能力,在kotlin/js上Dispatchers.Main等效于Dispatchers.Default;
  • kotlin/native:kotlin/native是一種將kotlin代碼編譯為無(wú)需虛擬機(jī)就可運(yùn)行的原生二進(jìn)制文件的技術(shù), 它的主要目的是允許對(duì)不需要或不可能使用虛擬機(jī)的平臺(tái)進(jìn)行編譯,例如嵌入式設(shè)備或iOS,在kotlin/native上Dispatchers.Main等效于Dispatchers.Default;
  • kotlin/JVM:kotlin/JVM就是需要虛擬機(jī)才能編譯的平臺(tái),例如Android就是屬于kotlin/JVM,對(duì)于kotlin/JVM我們需要引入對(duì)應(yīng)的dispatcher,例如Android就需要引入kotlinx-coroutines-android庫(kù),它里面有Android對(duì)應(yīng)的Dispatchers.Main實(shí)現(xiàn),其實(shí)就是把任務(wù)通過(guò)Handler運(yùn)行在Android的主線程.

我們?cè)倏碊ispatchers.Main的實(shí)現(xiàn) - MainDispatcherLoader.dispatcher:

internal object MainDispatcherLoader {
    
    @JvmField
    val dispatcher: MainCoroutineDispatcher = loadMainDispatcher()

    private fun loadMainDispatcher(): MainCoroutineDispatcher {
        //...主要是通過(guò)反射加載實(shí)現(xiàn)了MainCoroutineDispatcher的類(lèi)
    }
}

所以Dispatchers.Main的CoroutineDispatcher實(shí)現(xiàn)是MainCoroutineDispatcher,MainCoroutineDispatcher的具體實(shí)現(xiàn)就因平臺(tái)的不同而不同了,如果你直接使用Dispatchers.Main而沒(méi)有引入對(duì)應(yīng)的庫(kù)就會(huì)引發(fā)IllegalStateException異常。

3、CoroutineName

public data class CoroutineName(
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
   
    public companion object Key : CoroutineContext.Key<CoroutineName>

    override fun toString(): String = "CoroutineName($name)"
}

CoroutineName就是協(xié)程的名字,它的結(jié)構(gòu)很簡(jiǎn)單, 我們平時(shí)開(kāi)發(fā)一般是不會(huì)去指定一個(gè)CoroutineName的,因?yàn)镃oroutineName只在kotlin的調(diào)試模式下才會(huì)被用的, 它在debug模式下被用于設(shè)置協(xié)程運(yùn)行線程的名字:

internal data class CoroutineId(
    val id: Long
) : ThreadContextElement<String>, AbstractCoroutineContextElement(CoroutineId) {
   
    override fun updateThreadContext(context: CoroutineContext): String {
        val coroutineName = context[CoroutineName]?.name ?: "coroutine"
        val currentThread = Thread.currentThread()
        val oldName = currentThread.name
        var lastIndex = oldName.lastIndexOf(DEBUG_THREAD_NAME_SEPARATOR)
        if (lastIndex < 0) lastIndex = oldName.length
        currentThread.name = buildString(lastIndex + coroutineName.length + 10) {
            append(oldName.substring(0, lastIndex))
            append(DEBUG_THREAD_NAME_SEPARATOR)
            append(coroutineName)
            append('#')
            append(id)
        }
        return oldName
    }
  
  //...
}

4、CoroutineExceptionHandler

public interface CoroutineExceptionHandler : CoroutineContext.Element {
   
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>

    public fun handleException(context: CoroutineContext, exception: Throwable)
}

CoroutineExceptionHandler就是協(xié)程的異常處理器,用來(lái)處理協(xié)程運(yùn)行中未捕獲的異常,每一個(gè)創(chuàng)建的協(xié)程默認(rèn)都會(huì)有一個(gè)異常處理器,我們可以在啟動(dòng)協(xié)程時(shí)通過(guò)CoroutineContext指定我們自定義的異常處理器,我們可以通過(guò)CoroutineExceptionHandler方法創(chuàng)建一個(gè)CoroutineExceptionHandler,它會(huì)返回一個(gè)CoroutineExceptionHandler的默認(rèn)實(shí)現(xiàn),默認(rèn)實(shí)現(xiàn)的handleException方法中調(diào)用了我們傳進(jìn)的handler方法:

public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler =
    object : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler {
        override fun handleException(context: CoroutineContext, exception: Throwable) =
            handler.invoke(context, exception)
    }

CoroutineExceptionHandler只對(duì)launch方法啟動(dòng)的根協(xié)程有效,而對(duì)async啟動(dòng)的根協(xié)程無(wú)效,因?yàn)閍sync啟動(dòng)的根協(xié)程默認(rèn)會(huì)捕獲所有未捕獲異常并把它放在Deferred中,等到用戶(hù)調(diào)用Deferred的await方法才拋出,如下:

fun main(){
    //自定義CoroutineExceptionHandler
    val handler = CoroutineExceptionHandler{ coroutineContext, throwable ->
        println("my coroutineExceptionHandler catch exception, msg = ${throwable.message}")
    }

    //handler有效
    val job = GlobalScope.launch(handler){
        throw IndexOutOfBoundsException("exception thrown from launch")
    }
    job.start()

    //handler無(wú)效
    val deferred = GlobalScope.async(handler){
        throw NullPointerException("exception thrown from async")
    }
    deferred.start()

    Thread.sleep(1000)
}

輸出:

my coroutineExceptionHandler catch exception, msg = exception thrown from launch

其中只有l(wèi)aunch啟動(dòng)的根協(xié)程拋出的異常才被CoroutineExceptionHandler處理,而對(duì)于async啟動(dòng)的根協(xié)程拋出的異常CoroutineExceptionHandler無(wú)效,需要我們調(diào)用Deferred的await方法時(shí)try catch。

還有子協(xié)程拋出的未捕獲異常會(huì)委托父協(xié)程的CoroutineExceptionHandler處理,子協(xié)程設(shè)置的CoroutineExceptionHandler永遠(yuǎn)不會(huì)生效(SupervisorJob 除外),如下:

fun main(){
    //根協(xié)程的Handler
    val parentHandler = CoroutineExceptionHandler{coroutineContext, throwable ->
        println("parent coroutineExceptionHandler catch exception, msg = ${throwable.message}")
    }
    //啟動(dòng)根協(xié)程
    val parentJob = GlobalScope.launch(parentHandler){
        //子協(xié)程的Handler
        val childHandler = CoroutineExceptionHandler{coroutineContext, throwable ->
            println("child coroutineExceptionHandler catch exception, msg = ${throwable.message}")
        }
        //啟動(dòng)子協(xié)程
        val childJob = launch(childHandler){
            throw IndexOutOfBoundsException("exception thrown from child launch")
        }
        childJob.start()
    }
    parentJob.start()
    
    Thread.sleep(1000)
}

輸出:
parent coroutineExceptionHandler catch exception, msg = exception thrown from child launch

可以看到子協(xié)程設(shè)置CoroutineExceptionHandler沒(méi)有輸出,只有根協(xié)程的CoroutineExceptionHandler輸出了,但是也有例外,如果子協(xié)程是SupervisorJob,那么它設(shè)置的CoroutineExceptionHandler是生效的,前面也說(shuō)過(guò)SupervisorJob不會(huì)產(chǎn)生異常傳播。

當(dāng)父協(xié)程的子協(xié)程同時(shí)拋出多個(gè)異常時(shí),CoroutineExceptionHandler只會(huì)捕獲第一個(gè)協(xié)程拋出的異常,后續(xù)協(xié)程拋出的異常被保存在第一個(gè)異常的suppressed數(shù)組中,如下:

fun main(){
    val handler = CoroutineExceptionHandler{coroutineContext, throwable ->
        println("my coroutineExceptionHandler catch exception, msg = ${throwable.message}, suppressed = ${throwable.suppressed.contentToString()}")
    }
    val parentJob = GlobalScope.launch(handler){
        launch {
            try {
                delay(200)
            }finally {
                //第二個(gè)拋出的異常
                throw IndexOutOfBoundsException("exception thrown from first child launch")
            }
        }.start()

        launch {
            delay(100)
            //第一個(gè)拋出的異常
            throw NullPointerException("exception thrown from second child launch")
        }.start()
    }
    parentJob.start()

    Thread.sleep(1000)
}

輸出:
my coroutineExceptionHandler catch exception, msg = exception thrown from second child launch, suppressed = [java.lang.IndexOutOfBoundsException: exception thrown from first child launch]

可以看到CoroutineExceptionHandler只處理了第一個(gè)子協(xié)程拋出的異常,后續(xù)異常都放在了第一個(gè)拋出異常的suppressed數(shù)組中。

還有取消協(xié)程時(shí)會(huì)拋出一個(gè)CancellationException,它會(huì)被所有CoroutineExceptionHandler省略,但可以try catch它,同時(shí)當(dāng)子協(xié)程拋出CancellationException時(shí),并不會(huì)終止當(dāng)前父協(xié)程的運(yùn)行:

fun main(){
  val handler = CoroutineExceptionHandler{coroutineContext, throwable ->
        println("my coroutineExceptionHandler catch exception, msg = ${throwable.message}")
    }
    val parentJob = GlobalScope.launch(handler){
        val childJob = launch {
            try {
                delay(Long.MAX_VALUE)
            }catch (e: CancellationException){
                println("catch cancellationException thrown from child launch")
                println("rethrow cancellationException")
                throw CancellationException()
            }finally {
                println("child was canceled")
            }
        }
        //取消子協(xié)程
        childJob.cancelAndJoin()
        println("parent is still running")
    }
    parentJob.start()

    Thread.sleep(1000)
}

輸出:
catch cancellationException thrown from child launch
rethrow cancellationException
child was canceled
parent is still running

可以看到當(dāng)拋出CancellationException時(shí),我們可以try catch住它,同時(shí)當(dāng)我們?cè)俅螔伋鏊鼤r(shí),協(xié)程的CoroutineExceptionHandler并沒(méi)有處理它,同時(shí)父協(xié)程不受影響,繼續(xù)運(yùn)行。

上面就是CoroutineExceptionHandler處理協(xié)程異常時(shí)的特點(diǎn)。

CoroutineContext的結(jié)構(gòu)

我們?cè)俅慰匆幌翪oroutineContext的全家福:

image

上面講解了組成CoroutineContext的Element,每一個(gè)Element都繼承自CoroutineContext,而每一個(gè)Element都可以通過(guò) + 號(hào)來(lái)組合,也可以通過(guò)類(lèi)似map的 [key] 來(lái)取值,這和CoroutineContext的運(yùn)算符重載邏輯和它的結(jié)構(gòu)實(shí)現(xiàn)CombinedContext有關(guān),我們先來(lái)看一下CoroutineContext類(lèi):

public interface CoroutineContext {
   
    //操作符[]重載,可以通過(guò)CoroutineContext[Key]這種形式來(lái)獲取與Key關(guān)聯(lián)的Element
    public operator fun <E : Element> get(key: Key<E>): E?

    //它是一個(gè)聚集函數(shù),提供了從left到right遍歷CoroutineContext中每一個(gè)Element的能力,并對(duì)每一個(gè)Element做operation操作
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    //操作符+重載,可以CoroutineContext + CoroutineContext這種形式把兩個(gè)CoroutineContext合并成一個(gè)
    public operator fun plus(context: CoroutineContext): CoroutineContext
    
    //返回一個(gè)新的CoroutineContext,這個(gè)CoroutineContext刪除了Key對(duì)應(yīng)的Element
    public fun minusKey(key: Key<*>): CoroutineContext
  
    //Key定義,空實(shí)現(xiàn),僅僅做一個(gè)標(biāo)識(shí)
    public interface Key<E : Element>

   //Element定義,每個(gè)Element都是一個(gè)CoroutineContext
    public interface Element : CoroutineContext {
       
        //每個(gè)Element都有一個(gè)Key實(shí)例
        public val key: Key<*>
                
        //...
    }
}

除了plus方法,CoroutineContext中的其他三個(gè)方法都被CombinedContext、Element、EmptyCoroutineContext重寫(xiě),CombinedContext就是CoroutineContext集合結(jié)構(gòu)的實(shí)現(xiàn),它里面是一個(gè)遞歸定義,Element就是CombinedContext中的元素,而EmptyCoroutineContext就表示一個(gè)空的CoroutineContext,它里面是空實(shí)現(xiàn)。

1、CombinedContext

我們先看CombinedContext類(lèi):

//CombinedContext只包含left和element兩個(gè)成員:left可能為CombinedContext或Element實(shí)例,而element就是Element實(shí)例
internal class CombinedContext(
    private val left: CoroutineContext,
    private val element: Element
) : CoroutineContext, Serializable {
  
      //CombinedContext的get操作的邏輯是:
      //1、先看element是否是匹配,如果匹配,那么element就是需要找的元素,返回element,否則說(shuō)明要找的元素在left中,繼續(xù)從left開(kāi)始找,根據(jù)left是CombinedContext還是Element轉(zhuǎn)到2或3
      //2、如果left又是一個(gè)CombinedContext,那么重復(fù)1
      //3、如果left是Element,那么調(diào)用它的get方法返回
      override fun <E : Element> get(key: Key<E>): E? {
        var cur = this
        while (true) {
            //1
            cur.element[key]?.let { return it }
            val next = cur.left
            if (next is CombinedContext) {//2
                cur = next
            } else {//3
                return next[key]
            }
        }
    }

    //CombinedContext的fold操作的邏輯是:先對(duì)left做fold操作,把left做完fold操作的的返回結(jié)果和element做operation操作
    public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
        operation(left.fold(initial, operation), element)

    //CombinedContext的minusKey操作的邏輯是:
    //1、先看element是否是匹配,如果匹配,那么element就是需要?jiǎng)h除的元素,返回left,否則說(shuō)明要?jiǎng)h除的元素在left中,繼續(xù)從left中刪除對(duì)應(yīng)的元素,根據(jù)left是否刪除了要?jiǎng)h除的元素轉(zhuǎn)到2或3或4
    //2、如果left中不存在要?jiǎng)h除的元素,那么當(dāng)前CombinedContext就不存在要?jiǎng)h除的元素,直接返回當(dāng)前CombinedContext實(shí)例就行
    //3、如果left中存在要?jiǎng)h除的元素,刪除了這個(gè)元素后,left變?yōu)榱丝眨敲粗苯臃祷禺?dāng)前CombinedContext的element就行
    //4、如果left中存在要?jiǎng)h除的元素,刪除了這個(gè)元素后,left不為空,那么組合一個(gè)新的CombinedContext返回
    public override fun minusKey(key: Key<*>): CoroutineContext {
        //1
        element[key]?.let { return left }
        val newLeft = left.minusKey(key)
        return when {
            newLeft === left -> this//2
            newLeft === EmptyCoroutineContext -> element//3
            else -> CombinedContext(newLeft, element)//4
        }
    }
  
  //...
}

可以發(fā)現(xiàn)CombinedContext中的get、fold、minusKey操作都是遞歸形式的操作,遞歸的終點(diǎn)就是當(dāng)這個(gè)left是一個(gè)Element,我們?cè)倏碋lement類(lèi):

public interface Element : CoroutineContext {

    public val key: Key<*>
        
    //Element的get方法邏輯:如果key和自己的key匹配,那么自己就是要找的Element,返回自己,否則返回null
    public override operator fun <E : Element> get(key: Key<E>): E? =
        if (this.key == key) this as E else null

    //Element的fold方法邏輯:對(duì)傳入的initial和自己做operation操作
    public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
        operation(initial, this)

    //Element的minusKey方法邏輯:如果key和自己的key匹配,那么自己就是要?jiǎng)h除的Element,返回EmptyCoroutineContext(表示刪除了自己),否則說(shuō)明自己不需要被刪除,返回自己
    public override fun minusKey(key: Key<*>): CoroutineContext =
        if (this.key == key) EmptyCoroutineContext else this
}

現(xiàn)在我們把CombinedContext和Element結(jié)合來(lái)看,那么CombinedContext的整體結(jié)構(gòu)如下:

image

有點(diǎn)像是一個(gè)鏈表,left就是指向下一個(gè)結(jié)點(diǎn)的指針,有了這個(gè)圖我們?cè)購(gòu)恼w看當(dāng)調(diào)用CombinedContext的get、fold、minusKey操作時(shí)的訪問(wèn)順序:get、minusKey操作大體邏輯都是先訪問(wèn)當(dāng)前element,不滿(mǎn)足,再訪問(wèn)left的element,順序都是從right到left,而fold的操作大體邏輯是先訪問(wèn)left,直到遞歸到最后的element,然后再?gòu)膌eft到right的返回,從而訪問(wèn)了所有的element。

2、CoroutineContext的plus操作

現(xiàn)在我們來(lái)看CoroutineContext唯一沒(méi)有被重寫(xiě)的方法 - plus方法:

public interface CoroutineContext {
   
    //...
  
    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else 
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    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)
                    }
                }
            }
}

這個(gè)方法看起來(lái)有點(diǎn)復(fù)雜,為了方便我們理解,我把它簡(jiǎn)化一下,我把對(duì)ContinuationInterceptor的處理去掉,如下:

public interface CoroutineContext {
   
    //...
  
    public operator fun plus(context: CoroutineContext): CoroutineContext =
    //如果要相加的CoroutineContext為空,那么不做任何處理,直接返回
        if (context === EmptyCoroutineContext) this else 
        //如果要相加的CoroutineContext不為空,那么對(duì)它進(jìn)行fold操作
            context.fold(this) { acc, element -> //我們可以把a(bǔ)cc理解成+號(hào)左邊的CoroutineContext,element理解成+號(hào)右邊的CoroutineContext的某一個(gè)element
                //首先從左邊CoroutineContext中刪除右邊的這個(gè)element
                val removed = acc.minusKey(element.key)
                //如果removed為空,說(shuō)明左邊CoroutineContext刪除了和element相同的元素后為空,那么返回右邊的element即可
                if (removed === EmptyCoroutineContext) element else {
                    //如果removed不為空,說(shuō)明左邊CoroutineContext刪除了和element相同的元素后還有其他元素,那么構(gòu)造一個(gè)新的CombinedContext返回
                    return CombinedContext(removed, element)
                }
            }
}

plus方法大部分情況最終下返回一個(gè)CombinedContext,即我們把兩個(gè)CoroutineContext相加后,返回一個(gè)CombinedContext,在組合成CombinedContext時(shí),+號(hào)右邊的CoroutineContext中的元素會(huì)覆蓋+號(hào)左邊的CoroutineContext中的含有相同key的元素,如下:

(Dispatchers.Main, "name") + (Dispatchers.IO) = (Dispatchers.IO, "name")

這個(gè)覆蓋操作就在fold方法的參數(shù)operation代碼塊中完成,通過(guò)minusKey方法刪除掉重復(fù)元素,前面講過(guò)當(dāng)調(diào)用CombinedContext的fold方法時(shí),會(huì)從left到right到訪問(wèn)所有的element,即會(huì)從left到right的把每一個(gè)element傳入operation方法中,作為operation方法的第二個(gè)參數(shù),而operation方法第一個(gè)參數(shù)acc的初始值為fold方法傳入的initial值,然后它會(huì)不斷的更新,每次更新的值為上一次調(diào)用operation方法的返回值,所以當(dāng)兩個(gè)CoroutineContext相加時(shí),puls方法可以理解為下面的偽代碼:

 val acc = 左邊的CoroutineContext
  for(var element in 右邊的CoroutineContext){
    acc = operation(acc, element)//operation操作中會(huì)讓element覆蓋掉acc中與element相同的元素
  }
  return acc//所以plus方法最終返回的CoroutineContext是不存在key相同的element的

所以puls方法最終返回的CoroutineContext是不存在key相同的element的,+號(hào)右邊的CoroutineContext中的元素會(huì)覆蓋+號(hào)左邊的CoroutineContext中的含有相同key的元素,這像是Set的特性。

現(xiàn)在我們?cè)倏椿睾?jiǎn)化前的plus方法,它里面有個(gè)對(duì)ContinuationInterceptor的處理,目的是讓ContinuationInterceptor在每次相加后都能變成CoroutineContext中的最后一個(gè)元素, ContinuationInterceptor它也是繼承自Element,通常叫做協(xié)程上下文攔截器,它的主要作用是在協(xié)程執(zhí)行前攔截它,從而在協(xié)程執(zhí)行前做出一些其他的操作,前面我們講到CoroutineDispatcher它本身也繼承自ContinuationInterceptor,ContinuationInterceptor有一個(gè)interceptContinuation方法用于返回?cái)r截協(xié)程的行為,而這個(gè)行為就是前面我們所講到Dispatchers.Unconfined時(shí)的DispatchedContinuation,DispatchedContinuation在恢復(fù)協(xié)程前根據(jù)協(xié)程的CoroutineDispatcher類(lèi)型做出不同的協(xié)程分派行為,通過(guò)把ContinuationInterceptor放在最后面,協(xié)程在查找上下文的element時(shí),總能最快找到攔截器,避免了遞歸查找,從而讓攔截行為前置執(zhí)行。

結(jié)語(yǔ)

本文主要介紹了CoroutineContext的元素組成和結(jié)構(gòu),理解CoroutineContext對(duì)于理解協(xié)程使用有很大的幫助,因?yàn)閰f(xié)程的啟動(dòng)時(shí)就離不開(kāi)CoroutineContext,同時(shí)如果你以后想要更深入的學(xué)習(xí)協(xié)程,例如協(xié)程的創(chuàng)建過(guò)程,Continuation概念、suspend關(guān)鍵字等,本篇文章也能給你一個(gè)拋磚引玉的效果。

以上就是本文的所有內(nèi)容,希望大家有所收獲!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容