context,即协程上下文
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
根本应用
本文次要剖析CoroutineContext实现原理,并不会写太多应用上的内容。
CoroutineContext.Element有一个抽象类AbstractCoroutineContextElement有了它创立一个元素就很简略了,间接提供Key即可。
集体了解这种设计形式相似AbstractLis,利用一个抽象类,对接口的一些专用、高频办法进行一些默认的实现。有非凡需要独自实现。这样继承它用起来比拟不便
//Base class for [CoroutineContext.Element] implementations.
abstract class AbstractCoroutineContextElement{
public override val key: Key<*>
}: Element
创立元素并不难,提供对应的Key即可,咱们能够利用它来实现一个协程名
class CoroutineName(val name: String): AbstractCoroutineContextElement(CoroutineName.Key){
companion object Key: CoroutineContext.Key<CoroutineName>//伴生对象
override fun toString(): String = "CoroutineName($name)"
}
把定义好的协程上下文赋值给作为实现回调的Continuation(completion)实例,这样就能够将它绑定到协程上了。
fun test(){
suspend{
//协程体...
}.startCoroutine(MyContinuation())
}
class MyContinuation() : Continuation<Int> {
//绑定上下文,这里我成心设置两个name ,我就是玩儿
override val context: CoroutineContext = CoroutineName("Co-01") + CoroutineName("Co-02")
override fun resumeWith(result: Result<Int>) {
context[CoroutineName].toString()//上下文应用
}
}
在我学习上下文的时候,当我看到context[CoroutineName]
与CoroutineName("Co-01") + CoroutineName("Co-02")
我立刻意识到它重载了操作符,立马点开CoroutineContext源码,当场拘捕如下两个办法
public operator fun <E : Element> get(key: Key<E>): E?
public operator fun plus(context: CoroutineContext): CoroutineContext
不晓得你们想到了吗?如果没想到阐明对kotlin语法还不太熟悉哦 ~
当初能够间接拉到文章底部看总结的论断
从这里开始,全是论断的源码证实。
CoroutineContex剖析
伪代码构造
public interface CoroutineContext {
//重载get 实现context[key]用法
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
//重载plus 实现 + ,返回值是 CoroutineContext 类型。
//override val context: CoroutineContext = CoroutineName("Co-01") + CoroutineName("Co-02")
//这个办法十分要害,上面会具体解读
public operator fun plus(context: CoroutineContext): CoroutineContext...
//返回一个去除链表中key的对象的上下文。
public fun minusKey(key: Key<*>): CoroutineContext
//[CoroutineContext] 元素的键。 [E] 是一种具备此键的元素。E必须是Element的子类型,约等于 E extends Element.
public interface Key<E : Element>
// Element是上下文的元素接口,这里挺有意思它居然也实现了CoroutineContext
public interface Element : CoroutineContext {
/**
* 一个形象属性,让子类必须提供的,就是key么。标识
*/
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
}
}
下面就是预热一下,对这个接口的各个局部有个大略理解,看不懂没关系,上面会有残缺、零碎的源码剖析。
简略总结一下: 尽管Element 也是CoroutineContext 子类,然而它也重写了CoroutineContext 的办法,它跟CoroutineContext间接实现类是有显著区别的。
上下文数据结构
CombinedContext
internal class CombinedContext(
private val left: CoroutineContext,
private val element: Element
) : CoroutineContext, Serializable {
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]
}
}
}
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(left.fold(initial, operation), element)
public override fun minusKey(key: Key<*>): CoroutineContext {
//如果element[key]不等于空,即key相等,阐明element被命中了,element须要被摈弃,let是内联函数,所以等效于间接返回left。否则持续向下递归执行
//题外话:这就阐明为啥拦截器放在队尾,因为这样间接就返回了,效率很高省去迭代。当然不是在这里get,然而情理是一样的。顺便提一嘴。
element[key]?.let { return left }
val newLeft = left.minusKey(key)//递归到最原始的节点的left就是Element,如果命中相等就是EmptyCoroutineContext。
//假如咱们递归至最原始的left,left命中了。
return when {
newLeft === left -> this//如果没命中
//如果链表尾部命中,返回element即可。
newLeft === EmptyCoroutineContext -> element
//如果两头,或者头部命中。把断开的链子接上。
else -> CombinedContext(newLeft, element)
}
}
private fun size(): Int {
var cur = this
var size = 2
while (true) {
cur = cur.left as? CombinedContext ?: return size
size++
}
}
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)
}
}
}
override fun equals(other: Any?): Boolean =
this === other || other is CombinedContext && other.size() == size() && other.containsAll(this)
override fun hashCode(): Int = left.hashCode() + element.hashCode()
override fun toString(): String =
"[" + fold("") { acc, element ->
if (acc.isEmpty()) element.toString() else "$acc, $element"
} + "]"
}
CombinedContext是CoroutineContext的实现类
实现 CoroutineContext接口代表列表,实现CoroutineContext.Element的是增加到列表里的元素
CombinedContext 是一个递归列表,跟咱们常常用的List Node节点还有点不太一样。left指向上一个节点,但他们是嵌套关系,element代表以后元素
通过这幅图,要试着了解这种”嵌套“或者说”包裹“的关系。
实例2
能够了解为之前原有的上下文对象。My5CoroutineName
能够了解须要新插入列表中的Element,实例2
.plusMy5CoroutineName
后,创立并返回了实例1
,新增节点把老节点包起来存在left。跟双链表那种插入元素批改指针指向的形式有显著区别。
EmptyCoroutineContext
还有这个数据结构,空上下文。
public object EmptyCoroutineContext : CoroutineContext, Serializable {
private const val serialVersionUID: Long = 0
private fun readResolve(): Any = EmptyCoroutineContext
public override fun <E : Element> get(key: Key<E>): E? = null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = initial
public override fun plus(context: CoroutineContext): CoroutineContext = context
public override fun minusKey(key: Key<*>): CoroutineContext = this
public override fun hashCode(): Int = 0
public override fun toString(): String = "EmptyCoroutineContext"
}
残缺代码剖析
开始之前,再来重温一下这段代码
class MyContinuation() : Continuation<Int> {
override val context: CoroutineContext = CoroutineName("Co-01") + CoroutineName("Co-02")
override fun resumeWith(result: Result<Int>) {
context[CoroutineName].toString()//上下文应用
}
}
首先必定是不能不初始化context,所以要么是EmptyCoroutineContext,要么是Element。
其次CoroutineName 是 Element类型,CombinedContext是公有类。咱们不能结构一个CombinedContext赋值给context。
长期论断:
上下文列表的第一个CombinedContext的left 肯定是 Element
之所以定这个论断,是因为前面波及到很多递归代码,所以必须把完结递归的条件搞清楚。
残缺代码
咱们从下面的例程登程,来摸索一下CoroutineContext的数据结构,此次剖析代号K1.
案例K1
public interface CoroutineContext {
//有三处实现,别离是:Element、CombinedContext、EmptyCoroutineContext
public operator fun <E : Element> get(key: Key<E>): E?
//有三处实现,别离是:Element、CombinedContext、EmptyCoroutineContext
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
//只有这一处实现
//留神:假如CoroutineName("Co-01") + CoroutineName("Co-02") 缩写为: co1.plus(co2)那么上面的this指的是co1 context指的是co2
public operator fun plus(context: CoroutineContext): CoroutineContext =
//传入的元素是空那么没必要拼接间接返回以后(this)
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
//1,这里开始代码很芜杂,不要急,一步一步得捋。必定能轻松看明确
//2.首先花2分钟去下面认真看fold函数的定义,如果看不懂,把 CoroutineContext 当成R类型带入,你就明确上面传this的目标了。
//3.这里简略剖析一下第一个参数要求是 CoroutineContext 的对象,这里传了[this]就是以后的上下文对象
//第二个参数是一个函数,所以依据Lambda简化规定,能够间接跟着{...}.两个参数别离是 acc: CoroutineContext,element: Element
//4.想必你也猜出来了,我罗唆再强势一点,间接通知你acc代表[left],element 对应 [element]
//5.留神,这里调用flod的目标仅仅是定义了 Lambda 表达式,然而此处并没有调用这个表达式。我过后也被这个高阶函数搞懵一小下下。
//已知co1是Element类型,看下Element::fold的实现,恍然大悟了,持续
context.fold(this) { acc, element ->
//acc是co1 ,element元素是[传入的context] co2
//看看Element的minusKey,key相等,返回了EmptyCoroutineContext,这里就意味着把co1给删了,因为他们相等啊。
val removed = acc.minusKey(element.key)
//EmptyCoroutineContext 返回时示意key雷同,间接把新element返回了。即当初的context 就等于 CoroutineName("Co-02")。
//【K1】流程剖析到此结束。持续拐回去,剖析一下如果持续追加上下文,CoroutineName("Co-02") + DispatcherContext。会产生什么事件。
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)
}
}
}
/**
*返回一个去除链表中key的对象的上下文。
*/
public fun minusKey(key: Key<*>): CoroutineContext
/**
* Key 标识接口
*/
public interface Key<E : Element>
/**
* 上下文链表中元素标识接口
*/
public interface Element : CoroutineContext {
/**
* 这个元素对应的键
*/
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
/**
* 进行累加的高阶函数。因为element对象不是一个汇合对象,所以这里不会递归,只会执行一次 operation
*/
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
/**
* 如果以后key相等那么返回EmptyCoroutineContext。这个函数寓意就是用来去除key
*/
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key == key) EmptyCoroutineContext else this
}
}
在剖析一个状况:CoroutineName("Co-02") + DispatcherContext
此次剖析代号K2.
案例K2
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
//已知this为co2,是Element类型
context.fold(this) { acc, element ->
//acc是co2 ,element元素是[传入的拦截器]缩写为:DP
//看看Element的minusKey,key不相等,removed=co2
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {//进到这里。
//已知removed代表老列表,即co2.
//这里做的事件目标:因为拦截器开发中应用频繁,所以这里是做优化,把拦截器放在链表开端,减少获取拦截器时的效率
//查看 Element::get 可知key不雷同,remove是Name而element是ContinuationInterceptor,这里返回null
val interceptor = removed[ContinuationInterceptor]
//如果拦截器对象为空的话,间接把Dp(拦截器)拼接CombinedContext对象中。这时候人造在队尾。【K2】剖析实现。
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
持续:
CoroutineName("Co-02") + DispatcherContext + ExceptionHandlerContext
此次剖析代号K3.
案例K3
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
//已知this为CombinedContext(left:co2,element:disp),context 为 ExceptionHandlerContext,Elemnet类型
context.fold(this) { acc, element ->
//acc是CombinedContext(co2,disp) ,element元素是[传入的context] Ex
//看看CombinedContext的minusKey,key不相等,removed=co2
val removed = acc.minusKey(element.key)//能够去看CombinedContext::minusKey,做了简略正文
if (removed === EmptyCoroutineContext) element else {//没有命中,进到这里。
//已知removed代表老列表,即CombinedContext(left:co2,element:disp).
//这里做的事件目标:因为拦截器开发中应用频繁,所以这里是做优化,把拦截器放在链表开端,减少获取拦截器时的效率
//查看CombinedContext::get ,找到拦截器dp,
val interceptor = removed[ContinuationInterceptor]
//如果没有拦截器,间接加到队尾就行了。因为这样不影响拦截器在队尾的准则
if (interceptor == null) CombinedContext(removed, element) else {
//咱们之前设置dp,所以不为空。查看CombinedContext::minusKey,命中了dp,把除了dp其余的局部当作left返回了。
val left = removed.minusKey(ContinuationInterceptor)
//如果新left为空,那么就没必要保留他了。间接让它隐没。
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
//拦截器放链表最初
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
不晓得你们感触如何,反正我是要写到吐了。递归代码就是这样不易浏览。只能通过逐句解析,让本人尽量放弃相熟水平。
再来一个最色的剖析—–K4,当作本篇的一个终结
案例K4
val context1:CoroutineContext = CoroutineName("Co-01");
val context2:CoroutineContext = DispatcherContext + ExceptionHandlerContext;
....
override val context: CoroutineContext = context1 + context2;
这样之前的的剖析逻辑就全变了。context1 是 element,context2是CombinedContext
public operator fun plus(context: CoroutineContext): CoroutineContext =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
//已知this为CoroutineName(Element),context 为 CombinedContext(left: dp, element: Ex)
context.fold(this) { acc, element -> //fold 不再是 Element::fold 而是 CombinedContext::fold。
//acc是left.fold(initial, operation),又是递归,fk
/*即:dp.fold(acc:CoroutineName,operation)。
这又回到了 Element::fold ==> dp.fold 中 acc:CoroutineName,element this即dp */
//acc:CoroutineName不包含dp,所以remove 等于 acc:CoroutineName
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {//没有命中,进到这里。
//已知removed代表老列表,即acc:CoroutineName
val interceptor = removed[ContinuationInterceptor]
//如果没有拦截器,间接加到队尾CombinedContext(CoroutineName, dp) dp.fold 返回了CombinedContext(CoroutineName, dp)
if (interceptor == null) CombinedContext(removed, element) else {
val left = removed.minusKey(ContinuationInterceptor)
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
//拦截器放链表最初
CombinedContext(CombinedContext(left, element), interceptor)
}
}
}
//=============================CombinedContext::fold===========================================
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(left.fold(initial, operation), element)
//=============================Element::fold========================
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
持续往下:
//=============================CombinedContext::fold===========================================
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(CombinedContext(CoroutineName, dp), element)//element是Ex。
//回到下层递归
{ acc, element ->
//acc是CombinedContext(CoroutineName, dp),element:ex
//acc不包含 ex ,所以 remove 等于 CombinedContext(CoroutineName, dp)
val removed = acc.minusKey(element.key)
if (removed === EmptyCoroutineContext) element else {//进到这里。
//已知removed代表老列表,即CombinedContext(CoroutineName, dp)
val interceptor = removed[ContinuationInterceptor]//取出dp拦截器这一项
if (interceptor == null) CombinedContext(removed, element) else {
//进到这里
val left = removed.minusKey(ContinuationInterceptor)//CoroutineName
if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
//最初的返回值。left :CoroutineName,element : ex , interceptor: dp.【K4】剖析完结
CombinedContext(CombinedContext(left, element), interceptor)
}
}
论断
1.CoroutineName("Co-02") + EmptyCoroutineContext
后果:CoroutineName("Co-02")
。
2.CoroutineName("Co-01") + CoroutineName("Co-02") = CoroutineName("Co-02")
主动去重,新的替换老的
3.CoroutineName("Co-01") + Dispatchers.Main + ExceptionHandlerCoroutineContext = CoroutineName("Co-01") <- ExceptionHandlerCoroutineContext <- Dispatchers.Main
(拦截器ContinuationInterceptor肯定在链表尾部(最右侧),这样的益处是get间接拿不必向左遍历)
当context+element的时候
- 如果context等于empty,那么间接返回element
- 如果context含有与element的key雷同的元素,那么context删除老元素,增加新元素。
- 每增加一个新元素后,会检测链表中是否有“拦截器”如果有,则必须把拦截器放在队尾,升高get老本
-
当element为Element的类型时
- context + CoroutineName = CombinedContext(left: context,element: CoroutineName );
-
当element为CombinedContext时
- (context + CombinedContext(A,B,C)= CombinedContext(left:CombinedContext(left:CombinedContext(left: context,element: A ),element: B),element: C);
- 下面的式子只是想阐明,[addAll]操作并不是简略的通过更改链表指针的形式实现,取出指标列表中每一个element,创立新的CombinedContext键入到原来的上下文表中。从头到尾通过递归,全部都是新创建的节点。
发表回复