<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
表示一個元素或者是元素集合的介面。它有一個Key(索引)的Element範例集合,每一個Element的範例也是一個CoroutineContext,即集合中每個元素也是集合。
如下圖所示,CoroutineContext的常見官方實現有以下幾種(少見的或者自定義的實現就不列舉,以後再聊):
Element類也是繼承自CoroutineContext介面的,該類的作用是給子類保留一個Key成員變數,用於在集合查詢的時候可以快速查詢到目標coroutineContext,Key成員變數是一個泛型變數,每個繼承自Element的子類都會去覆蓋實現Key成員變數(一般是使用子類自己去覆蓋Key),就比如拿最簡單的CoroutineName類來舉例子:
public data class CoroutineName( /** * User-defined coroutine name. */ val name: String ) : AbstractCoroutineContextElement(CoroutineName) { /** * Key for [CoroutineName] instance in the coroutine context. */ public companion object Key : CoroutineContext.Key<CoroutineName> /** * Returns a string representation of the object. */ override fun toString(): String = "CoroutineName($name)" } @SinceKotlin("1.3") public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element /** * Key for the elements of [CoroutineContext]. [E] is a type of element with this key. */ public interface Key<E : Element> /** * An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself. */ public interface Element : CoroutineContext { /** * A key of this coroutine context element. */ public val key: Key<*> public override operator fun <E : Element> get(key: Key<E>): E? = @Suppress("UNCHECKED_CAST") if (this.key == key) this as E else null 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 }
上面的CoroutineName建構函式定義為
public data class CoroutineName( /** * User-defined coroutine name. */ val name: String ) : AbstractCoroutineContextElement(CoroutineName)
父類別建構函式中傳遞的引數是CoroutineName,但是我們發現CoroutineName也不是Key介面public interface Key<E : Element>
的實現,為啥可以這樣直接傳遞呢?但是我們仔細看發現CoroutineName類定義了伴生物件: public companion object Key : CoroutineContext.Key<CoroutineName>
,在kotlin中伴生物件是可以直接省略 類.companion.
呼叫方式的,CoroutineName類也就代表著伴生物件,所以可以直接作為CoroutineName父類別建構函式的引數,神奇的kotlin語法搞得我一愣一愣的。
類似的還有Job,CoroutineDIspatcher,CoroutineExceptionHandler的成員變數Key的覆蓋實現:
//Job public interface Job : CoroutineContext.Element { /** * Key for [Job] instance in the coroutine context. */ public companion object Key : CoroutineContext.Key<Job> { //省略 } //省略 } //CoroutineExceptionHandler public interface CoroutineExceptionHandler : CoroutineContext.Element { /** * Key for [CoroutineExceptionHandler] instance in the coroutine context. */ public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler> //省略 } // CoroutineDIspatcher @SinceKotlin("1.3") public interface ContinuationInterceptor : CoroutineContext.Element { /** * The key that defines *the* context interceptor. */ companion object Key : CoroutineContext.Key<ContinuationInterceptor> }
CoroutineContext的操作符??有點莫名其妙的感覺,僅僅憑藉我的直覺的話很難理解,但是平常使用協程的過程中,我們經常會使用這些相關的操作符,比如 +,[]
等等符號,下面程式碼範例:
val comb = Job() + CoroutineName("") val cName = comb[CoroutineName]
上面的+
代表兩個coroutineContext合併到集合中,這裡的集合實際上是一個連結串列,後面會講到。
上面的[]
代表著從集合中索引出CoroutineName型別的CoroutineContext,這裡也可以看出來僅僅通過key就查詢出元素和map很相似,那麼可以知道value是唯一的。key都是coroutineContext子類作為泛型型別的,具有唯一性,那也可以間接推斷出上面+
操作其實也會覆蓋擁有相同key的value的值。
還有其他操作函數:fold
展開操作, minusKey
刪除集合中存在的元素。
還有一個問題就是,這個集合到底是什麼型別的集合,已經如何管理的,我們來一一解答:
CoroutineConetxt集合是連結串列結構的集合,是一個從本節點開始,向左遍歷parent節點的一個連結串列,節點的都是CoroutineContext的子類,分為Element,CombinedContext,EmptyCoroutineContext三種。
有以下程式碼作為舉例:
val scope = CoroutineScope(CoroutineName("") + Job() + CoroutineExceptionHandler{<!--{C}%3C!%2D%2D%20%2D%2D%3E--> _, _ -> } + Dispatchers.Default)
假如CoroutineScope自己的coroutineContext變數集合中是包含CoroutineName,Job,CoroutineExceptionHanlder,CoroutineDIspatcher四種上下文的,那麼他們組成的集合結構可能就會是下圖所示的連結串列結構,
使用scope查詢對應的Job的話直接呼叫scope[Job]
方法,替代Job的話呼叫 scope + Job()
,看原始碼就是使用scope的上下文集合替換Job
public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope = ContextScope(coroutineContext + context)
為啥是連結串列結構的集合呢,接下來直接看原始碼就知道了。
我們集合的連結串列結構,每個節點都是CombinedContext型別,裡面包含了element,left兩個成員變數,left指向連結串列的左邊,element表示當前節點的上下文元素(一般是job,name,handler,dispatcher四種),連結串列的最左端節點一定是Element元素
主要實現在combinedContext,Element元素的方法實現比較簡單,不單獨列舉。
建構函式
@SinceKotlin("1.3") internal class CombinedContext( private val left: CoroutineContext, private val element: Element ) : CoroutineContext, Serializable {
get函數:
//Element public override operator fun <E : Element> get(key: Key<E>): E? = @Suppress("UNCHECKED_CAST") if (this.key == key) this as E else null //CombinedContext 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] } } }
在程式碼中一般不會使用get方法,而是使用context[key]
來代替,類似於map集合的查詢。上下文是Element型別,key是對應型別那麼返回當前Element,不是當前型別,返回null;上下文是CombinedContext型別,指標cur指向當前節點,while迴圈開始,當前的element元素的key查詢到了,那麼就返回當前combinedContext,如果沒找到,那麼將指標指向left節點,如果left節點是combinedContext型別,那麼重複上述操作,如果是Element型別直接判斷是否可以查詢到key值。那麼從這裡看出連結串列的最左端元素一定是Element節點。
contain函數
private fun contains(element: Element): Boolean = get(element.key) == element private fun containsAll(context: CombinedContext): Boolean { var cur = context while (true) { if (!contains(cur.element)) return false val next = cur.left if (next is CombinedContext) { cur = next } else { return contains(next as Element) } } }
類似於get操作,contains函數直接呼叫get方法來判斷元素是不是和傳入引數相等。
containAll函數就是遍歷引數的連結串列節點是不是都包含在當前連結串列中。
fold函數
//coroutineContext public fun <R> fold(initial: R, operation: (R, Element) -> R): R
從表面意思就是展開操作,第一個入參 CoroutineContext,第二個入參 lambda表示式 用表示式的兩個引數CoroutineContext, Element 返回一個新的 CoroutineContext:
operation :(R , Element) -> R
Job.fold(CoroutineName("測試"),{ coroutineContext , element -> TODO("return new CoroutineContext") }) //example //Element public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(initial, this)
作為receiver的上下文是Element,呼叫fold的話,是讓ELement和入參的CoroutineContext作為lambda表示式 的兩個引數呼叫該lambda表示式返回結果。
MainScope().coroutineContext.fold(Job(),{ coroutineContext , element -> TODO("return new CoroutineContext") }) //example //CombinedContext public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(left.fold(initial, operation), element)
作為receiver的上下文是CombinedContext,呼叫fold的話,是讓left深度遞迴呼叫fold函數,一直到連結串列的最左端節點,我們知道連結串列的最左端節點一定是Element,那麼根據上面的程式碼,Element的fold函數內呼叫operation返回一個CoroutineContext後,遞迴回溯到上一層,繼續呼叫operation返回一個CoroutineContext,繼續回溯,一直回溯到開始呼叫MainScope().coroutineContext.fold(Job
的地方。如下圖所示:
minusKey函數
該函數的意思是從上下文集合中刪除key對應的上下文。
//Element public override fun minusKey(key: Key<*>): CoroutineContext = if (this.key == key) EmptyCoroutineContext else this
接收者receiver是Element型別的話,如果入參key和receiver是相等的話,那麼返回EmptyCoroutineContext空上下文,否則返回receiver本身。(可見找到得到key的話會返回空上下文,找不到的話返回本身)
//CombinedContext public override fun minusKey(key: Key<*>): CoroutineContext { element[key]?.let { return left } val newLeft = left.minusKey(key) return when { newLeft === left -> this newLeft === EmptyCoroutineContext -> element else -> CombinedContext(newLeft, element) } }
接收者receiver是CombinedContext型別的話,
left.minusKey(key)
,返回的newLeft有三種情況:上述操作,都只是在連結串列上跳過節點,然後將跳過的節點左節點left和右節點建立新的CombinedContext,產生一個新的連結串列出來。
操作例子:
刪除最左端節點
刪除中間節點:
結論:minusKey的操作只是將原始連結串列集合中排除某一個節點,然後複製一個連結串列返回,所以並不會影響原始集合
plus函數
該函數重寫+
操作符,函數定義operator fun plus(context: CoroutineContext): CoroutineContext
,作用是對上下文集合進行新增(相同會覆蓋)指定上下文操作。這個函數只有CoroutineContext實現了,程式碼如下:
/** * Returns a context containing elements from this context and elements from other [context]. * The elements from this context with the same key as in the other one are dropped. */ 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) } } }
入參如果是EmptyContext,那麼直接返回;不是空的話,對入參進行fold
操作,上面講了fold操作是將context連結串列展開,從連結串列最左端開始向context回溯呼叫fold函數的入參lambda表示式。那麼我們就知道了![A (CombineContext) + B(CombineContext)](https://img-blog.csdnimg.cn/40fb2861842a4c8f8c918752e3f89fd0.png)
是如何操作的了,首先B作為plus的入參,那麼B先展開到B連結串列結構的最左端,然後執行lambda操作{ acc, element -> ... }
, 這個lambda裡面
第一步
context.fold(this) { acc, element -> acc.minusKey(Element.Key) // ... } //CombineContext的實現 public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(left.fold(initial, operation), element)
根據CombineContext的實現,知道lambda的acc引數是A (CombineContext)
,element引數是B(CombineContext)
的fold遞迴的當前位置的element的元素,acc.minusKey(Element.Key)
所做的事情就是移除A (CombineContext)
連結串列中的B(CombineContext)
的element元素。
第二步
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] // ... }
第一步移除掉element之後,判斷剩餘的removed連結串列是不是empty的,如果為空,返回B(CombineContext)
的fold遞迴位置的element元素;不為空,接著從removed連結串列中獲取ContinuationInterceptor上下文(也就是dispatcher)。
第三步
if (interceptor == null) CombinedContext(removed, element) else { val left = removed.minusKey(ContinuationInterceptor) if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else CombinedContext(CombinedContext(left, element), interceptor) }
獲取的interceptor為空,那將element和removed連結串列構造出一個新的CombinedContext節點返回;如果不為空,從removed連結串列中移除interceptor返回一個不包含interceptor的連結串列left;移除後left連結串列為空,那麼將element和interceptor構造出一個新的CombinedContext節點返回;left連結串列不為空,那麼將left, element構造出一個新的CombinedContext節點,將新的CombinedContext節點和interceptor早構造出一個新的節點返回。
每一層遞迴fold操作結束後,返回一個新的context給上一層繼續遞迴,直到結束為止。
操作例子圖如下:
有如下兩個集合A (CombineContext) + B(CombineContext)
:
第一次遞迴回溯:
第二次遞迴回溯:
回溯深度取決於入參B的連結串列長度,B有多長回溯就會發生幾次,這裡沒有加入interceptor上下文元素,減少畫圖複雜度。
plus操作結論:
1.發現每次返回節點的時候,都會將interceptor移除後,放到節點的最右邊的位置,可以知道interceptor一定在連結串列的頭部;
2.lambda表示式中,一定會先移除掉相同key的上下文元素,然後用後加入的element和left連結串列新建一個CombinedContext節點插入到頭部
3.plus操作會覆蓋掉有相同key的上下文元素
經過對上面的原始碼的分析,可以推斷出一些上下文元素的操作符操作後,集合的元素排列狀態。比如下面操作:
private fun test() { val coroutineContext = Job() + CoroutineName("name1") + Dispatchers.IO + CoroutineExceptionHandler{ c,e -> } Log.i(TAG, "coroutineContext $coroutineContext") val newContext = coroutineContext + SupervisorJob() Log.i(TAG, "newContext $newContext") val newContext2 = newContext + (Job() + CoroutineName("name2")) Log.i(TAG, "newContext2 $newContext2") Log.i(TAG, "newContext2[CoroutineName] ${newContext2[CoroutineName]}") }
列印的紀錄檔如下:
I/MainActivity: coroutineContext [
JobImpl{Active}@b32c44,
CoroutineName(name1),
com.meeting.kotlinapplication.MainActivity$test$$inlined$CoroutineExceptionHandler$1@45ec12d,
Dispatchers.IO
]I/MainActivity: newContext [
CoroutineName(name1),
com.meeting.kotlinapplication.MainActivity$test$$inlined$CoroutineExceptionHandler$1@45ec12d,
SupervisorJobImpl{Active}@1022662,
Dispatchers.IO
]
I/MainActivity: newContext2 [
com.meeting.kotlinapplication.MainActivity$test$$inlined$CoroutineExceptionHandler$1@45ec12d,
JobImpl{Active}@76863f3,
CoroutineName(name2),
Dispatchers.IO
]I/MainActivity: newContext2[CoroutineName] CoroutineName(name2)
I/MainActivity: coroutineContext [
JobImpl{Active}@b32c44,
CoroutineName(name1),
com.meeting.kotlinapplication.MainActivity$test$$inlined$CoroutineExceptionHandler$1@45ec12d,
Dispatchers.IO
]
可以看出來:
1. Dispatchers元素一定是在連結串列的頭部;
2. 重複key的元素會被後加入的元素覆蓋,集合中不存在重複key的元素;
3. +
操作後返回新的連結串列集合,不會影響原始集合連結串列結構
上面總結的這些性質,可以很好的為job協程的父子關係,子job繼承父job的上下文集合這些特性,下一篇我將講解 協程Job父子關係的原理。
到此這篇關於Kotlin coroutineContext原始碼層深入分析的文章就介紹到這了,更多相關Kotlin coroutineContext內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45