您好,登录后才能下订单哦!
这篇“Kotlin协程上下文与上下文元素实例分析”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Kotlin协程上下文与上下文元素实例分析”文章吧。
EmptyCoroutineContext代表空上下文,由于自身为空,因此get方法的返回值是空的,fold方法直接返回传入的初始值,plus方法也是直接返回传入的context,minusKey方法返回自身,代码如下:
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" }
CombinedContext是组合上下文,是存储Element的重要的数据结构。内部存储的组织结构如下图所示:
可以看出CombinedContext是一种左偏(从左向右计算)的列表,这么设计的目的是为了让CoroutineContext中的plus方法工作起来更加自然。
由于采用这种数据结构,CombinedContext类中的很多方法都是通过循环实现的,代码如下:
internal class CombinedContext( // 数据结构左边可能为一个Element对象或者还是一个CombinedContext对象 private val left: CoroutineContext, // 数据结构右边只能为一个Element对象 private val element: Element ) : CoroutineContext, Serializable { override fun <E : Element> get(key: Key<E>): E? { var cur = this while (true) { // 进行get操作,如果当前CombinedContext对象中存在,则返回 cur.element[key]?.let { return it } // 获取左边的上下文对象 val next = cur.left // 如果是CombinedContext对象 if (next is CombinedContext) { // 赋值,继续循环 cur = next } else { // 如果不是CombinedContext对象 // 进行get操作,返回 return next[key] } } } // 数据结构左右分开操作,从左到右进行fold运算 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对象,则返回左边 element[key]?.let { return left } // 调用左边的minusKey方法 val newLeft = left.minusKey(key) return when { // 这种情况,说明左边部分已经是去掉指定的Element对象的,右边也是如此,因此返回当前对象,不需要在进行包裹 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++ } } // 通过get方法实现 private fun contains(element: Element): Boolean = get(element.key) == element private fun containsAll(context: CombinedContext): Boolean { var cur = context // 循环展开每一个CombinedContext对象,每个CombinedContext对象中的Element对象都要包含 while (true) { if (!contains(cur.element)) return false val next = cur.left if (next is CombinedContext) { cur = next } else { return contains(next as Element) } } } ... }
Key接口与Element接口定义在CoroutineContext接口中,代码如下:
public interface Key<E : Element> public interface Element : CoroutineContext { // 一个Key对应着一个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 // 自身与初始值进行fold操作 public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = operation(initial, this) // 如果要去除的是当前的Element对象,则返回空的上下文,否则返回自身 public override fun minusKey(key: Key<*>): CoroutineContext = if (this.key == key) EmptyCoroutineContext else this }
CoroutineContext接口定义了协程上下文的基本行为以及Key和Element接口。同时,重载了"+"操作,相关代码如下:
public interface CoroutineContext { public operator fun <E : Element> get(key: Key<E>): E? public fun <R> fold(initial: R, operation: (R, Element) -> R): R public operator fun plus(context: CoroutineContext): CoroutineContext = // 如果要与空上下文相加,则直接但会当前对象, if (context === EmptyCoroutineContext) this else // 当前Element作为初始值 context.fold(this) { acc, element -> // acc:已经加完的CoroutineContext对象 // element:当前要加的CoroutineContext对象 // 获取从acc中去掉element后的上下文removed,这步是为了确保添加重复的Element时,移动到最右侧 val removed = acc.minusKey(element.key) // 去除掉element后为空上下文(说明acc中只有一个Element对象),则返回element if (removed === EmptyCoroutineContext) element else { // ContinuationInterceptor代表拦截器,也是一个Element对象 // 下面的操作是为了把拦截器移动到上下文的最右端,为了方便快速获取 // 从removed中获取拦截器 val interceptor = removed[ContinuationInterceptor] // 若上下文中没有拦截器,则进行累加(包裹成CombinedContext对象),返回 if (interceptor == null) CombinedContext(removed, element) else { // 若上下文中有拦截器 // 获取上下文中移除到掉拦截器后的上下文left val left = removed.minusKey(ContinuationInterceptor) // 若移除到掉拦截器后的上下文为空上下文,说明上下文left中只有一个拦截器, // 则进行累加(包裹成CombinedContext对象),返回 if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else // 否则,现对当前要加的element和left进行累加,然后在和拦截器进行累加 CombinedContext(CombinedContext(left, element), interceptor) } } } public fun minusKey(key: Key<*>): CoroutineContext ... // (Key和Element接口) }
1.plus方法图解
假设我们有一个上下文顺序为A、B、C,现在要按顺序加上D、C、A。
1)初始值A、B、C
2)加上D
3)加上C
4)加上A
2.为什么要将ContinuationInterceptor放到协程上下文的最右端?
在协程中有大量的场景需要获取ContinuationInterceptor。根据之前分析的CombinedContext的minusKey方法,ContinuationInterceptor放在上下文的最右端,可以直接获取,不需要经过多次的循环。
AbstractCoroutineContextElement实现了Element接口,将Key对象作为构造方法必要的参数。
public abstract class AbstractCoroutineContextElement(public override val key: Key<*>) : Element
AbstractCoroutineContextKey用于实现Element的多态。什么是Element的多态呢?假设类A实现了Element接口,Key为A。类B继承自类A,Key为B。这时将类B的对象添加到上下文中,通过指定不同的Key(A或B),可以得到不同类型对象。具体代码如下:
// baseKey为衍生类的基类的Key // safeCast用于对基类进行转换 // B为基类,E为衍生类 public abstract class AbstractCoroutineContextKey<B : Element, E : B>( baseKey: Key<B>, private val safeCast: (element: Element) -> E? ) : Key<E> { // 顶置Key,如果baseKey是AbstractCoroutineContextKey,则获取baseKey的顶置Key private val topmostKey: Key<*> = if (baseKey is AbstractCoroutineContextKey<*, *>) baseKey.topmostKey else baseKey // 用于类型转换 internal fun tryCast(element: Element): E? = safeCast(element) // 用于判断当前key是否是指定key的子key // 逻辑为与当前key相同,或者与当前key的顶置key相同 internal fun isSubKey(key: Key<*>): Boolean = key === this || topmostKey === key }
getPolymorphicElement方法与minusPolymorphicKey方法
如果衍生类使用了AbstractCoroutineContextKey,那么基类在实现Element接口中的get方法时,就需要通过getPolymorphicElement方法,实现minusKey方法时,就需要通过minusPolymorphicKey方法,代码如下:
public fun <E : Element> Element.getPolymorphicElement(key: Key<E>): E? { // 如果key是AbstractCoroutineContextKey if (key is AbstractCoroutineContextKey<*, *>) { // 如果key是当前key的子key,则基类强制转换成衍生类,并返回 @Suppress("UNCHECKED_CAST") return if (key.isSubKey(this.key)) key.tryCast(this) as? E else null } // 如果key不是AbstractCoroutineContextKey // 如果key相等,则强制转换,并返回 @Suppress("UNCHECKED_CAST") return if (this.key === key) this as E else null }
public fun Element.minusPolymorphicKey(key: Key<*>): CoroutineContext { // 如果key是AbstractCoroutineContextKey if (key is AbstractCoroutineContextKey<*, *>) { // 如果key是当前key的子key,基类强制转换后不为空,说明当前Element需要去掉,因此返回空上下文,否则返回自身 return if (key.isSubKey(this.key) && key.tryCast(this) != null) EmptyCoroutineContext else this } // 如果key不是AbstractCoroutineContextKey // 如果key相等,说明当前Element需要去掉,因此返回空上下文,否则返回自身 return if (this.key === key) EmptyCoroutineContext else this }
以上就是关于“Kotlin协程上下文与上下文元素实例分析”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。