经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Kotlin » 查看文章
Kotlin协程上下文与上下文元素深入理解
来源:jb51  时间:2022/8/1 13:08:51  对本文有异议

一.EmptyCoroutineContext

EmptyCoroutineContext代表空上下文,由于自身为空,因此get方法的返回值是空的,fold方法直接返回传入的初始值,plus方法也是直接返回传入的context,minusKey方法返回自身,代码如下:

  1. public object EmptyCoroutineContext : CoroutineContext, Serializable {
  2. private const val serialVersionUID: Long = 0
  3. private fun readResolve(): Any = EmptyCoroutineContext
  4. public override fun <E : Element> get(key: Key<E>): E? = null
  5. public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = initial
  6. public override fun plus(context: CoroutineContext): CoroutineContext = context
  7. public override fun minusKey(key: Key<*>): CoroutineContext = this
  8. public override fun hashCode(): Int = 0
  9. public override fun toString(): String = "EmptyCoroutineContext"
  10. }

二.CombinedContext

CombinedContext是组合上下文,是存储Element的重要的数据结构。内部存储的组织结构如下图所示:

可以看出CombinedContext是一种左偏(从左向右计算)的列表,这么设计的目的是为了让CoroutineContext中的plus方法工作起来更加自然。

由于采用这种数据结构,CombinedContext类中的很多方法都是通过循环实现的,代码如下:

  1. internal class CombinedContext(
  2. // 数据结构左边可能为一个Element对象或者还是一个CombinedContext对象
  3. private val left: CoroutineContext,
  4. // 数据结构右边只能为一个Element对象
  5. private val element: Element
  6. ) : CoroutineContext, Serializable {
  7. override fun <E : Element> get(key: Key<E>): E? {
  8. var cur = this
  9. while (true) {
  10. // 进行get操作,如果当前CombinedContext对象中存在,则返回
  11. cur.element[key]?.let { return it }
  12. // 获取左边的上下文对象
  13. val next = cur.left
  14. // 如果是CombinedContext对象
  15. if (next is CombinedContext) {
  16. // 赋值,继续循环
  17. cur = next
  18. } else { // 如果不是CombinedContext对象
  19. // 进行get操作,返回
  20. return next[key]
  21. }
  22. }
  23. }
  24. // 数据结构左右分开操作,从左到右进行fold运算
  25. public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
  26. operation(left.fold(initial, operation), element)
  27. public override fun minusKey(key: Key<*>): CoroutineContext {
  28. // 如果右边是指定的Element对象,则返回左边
  29. element[key]?.let { return left }
  30. // 调用左边的minusKey方法
  31. val newLeft = left.minusKey(key)
  32. return when {
  33. // 这种情况,说明左边部分已经是去掉指定的Element对象的,右边也是如此,因此返回当前对象,不需要在进行包裹
  34. newLeft === left -> this
  35. // 这种情况,说明左边部分包含指定的Element对象,因此返回只右边
  36. newLeft === EmptyCoroutineContext -> element
  37. // 这种情况,返回的左边部分是新的,因此需要和右边部分一起包裹后,再返回
  38. else -> CombinedContext(newLeft, element)
  39. }
  40. }
  41. private fun size(): Int {
  42. var cur = this
  43. //左右各一个
  44. var size = 2
  45. while (true) {
  46. cur = cur.left as? CombinedContext ?: return size
  47. size++
  48. }
  49. }
  50. // 通过get方法实现
  51. private fun contains(element: Element): Boolean =
  52. get(element.key) == element
  53. private fun containsAll(context: CombinedContext): Boolean {
  54. var cur = context
  55. // 循环展开每一个CombinedContext对象,每个CombinedContext对象中的Element对象都要包含
  56. while (true) {
  57. if (!contains(cur.element)) return false
  58. val next = cur.left
  59. if (next is CombinedContext) {
  60. cur = next
  61. } else {
  62. return contains(next as Element)
  63. }
  64. }
  65. }
  66. ...
  67. }

三.Key与Element

Key接口与Element接口定义在CoroutineContext接口中,代码如下:

  1. public interface Key<E : Element>
  2. public interface Element : CoroutineContext {
  3. // 一个Key对应着一个Element对象
  4. public val key: Key<*>
  5. // 相等则强制转换并返回,否则则返回空
  6. public override operator fun <E : Element> get(key: Key<E>): E? =
  7. @Suppress("UNCHECKED_CAST")
  8. if (this.key == key) this as E else null
  9. // 自身与初始值进行fold操作
  10. public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
  11. operation(initial, this)
  12. // 如果要去除的是当前的Element对象,则返回空的上下文,否则返回自身
  13. public override fun minusKey(key: Key<*>): CoroutineContext =
  14. if (this.key == key) EmptyCoroutineContext else this
  15. }

四.CoroutineContext

CoroutineContext接口定义了协程上下文的基本行为以及Key和Element接口。同时,重载了"+"操作,相关代码如下:

  1. public interface CoroutineContext {
  2. public operator fun <E : Element> get(key: Key<E>): E?
  3. public fun <R> fold(initial: R, operation: (R, Element) -> R): R
  4. public operator fun plus(context: CoroutineContext): CoroutineContext =
  5. // 如果要与空上下文相加,则直接但会当前对象,
  6. if (context === EmptyCoroutineContext) this else
  7. // 当前Element作为初始值
  8. context.fold(this) { acc, element ->
  9. // acc:已经加完的CoroutineContext对象
  10. // element:当前要加的CoroutineContext对象
  11. // 获取从acc中去掉element后的上下文removed,这步是为了确保添加重复的Element时,移动到最右侧
  12. val removed = acc.minusKey(element.key)
  13. // 去除掉element后为空上下文(说明acc中只有一个Element对象),则返回element
  14. if (removed === EmptyCoroutineContext) element else {
  15. // ContinuationInterceptor代表拦截器,也是一个Element对象
  16. // 下面的操作是为了把拦截器移动到上下文的最右端,为了方便快速获取
  17. // 从removed中获取拦截器
  18. val interceptor = removed[ContinuationInterceptor]
  19. // 若上下文中没有拦截器,则进行累加(包裹成CombinedContext对象),返回
  20. if (interceptor == null) CombinedContext(removed, element) else {
  21. // 若上下文中有拦截器
  22. // 获取上下文中移除到掉拦截器后的上下文left
  23. val left = removed.minusKey(ContinuationInterceptor)
  24. // 若移除到掉拦截器后的上下文为空上下文,说明上下文left中只有一个拦截器,
  25. // 则进行累加(包裹成CombinedContext对象),返回
  26. if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
  27. // 否则,现对当前要加的element和left进行累加,然后在和拦截器进行累加
  28. CombinedContext(CombinedContext(left, element), interceptor)
  29. }
  30. }
  31. }
  32. public fun minusKey(key: Key<*>): CoroutineContext
  33. ... // (Key和Element接口)
  34. }
  • 1.plus方法图解

假设我们有一个上下文顺序为A、B、C,现在要按顺序加上D、C、A。

1)初始值A、B、C

2)加上D

3)加上C

4)加上A

  • 2.为什么要将ContinuationInterceptor放到协程上下文的最右端?

在协程中有大量的场景需要获取ContinuationInterceptor。根据之前分析的CombinedContext的minusKey方法,ContinuationInterceptor放在上下文的最右端,可以直接获取,不需要经过多次的循环。

五.AbstractCoroutineContextKey与AbstractCoroutineContextElement

AbstractCoroutineContextElement实现了Element接口,将Key对象作为构造方法必要的参数。

  1. 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),可以得到不同类型对象。具体代码如下:

  1. // baseKey为衍生类的基类的Key
  2. // safeCast用于对基类进行转换
  3. // B为基类,E为衍生类
  4. public abstract class AbstractCoroutineContextKey<B : Element, E : B>(
  5. baseKey: Key<B>,
  6. private val safeCast: (element: Element) -> E?
  7. ) : Key<E> {
  8. // 顶置Key,如果baseKey是AbstractCoroutineContextKey,则获取baseKey的顶置Key
  9. private val topmostKey: Key<*> = if (baseKey is AbstractCoroutineContextKey<*, *>) baseKey.topmostKey else baseKey
  10. // 用于类型转换
  11. internal fun tryCast(element: Element): E? = safeCast(element)
  12. // 用于判断当前key是否是指定key的子key
  13. // 逻辑为与当前key相同,或者与当前key的顶置key相同
  14. internal fun isSubKey(key: Key<*>): Boolean = key === this || topmostKey === key
  15. }

getPolymorphicElement方法与minusPolymorphicKey方法

如果衍生类使用了AbstractCoroutineContextKey,那么基类在实现Element接口中的get方法时,就需要通过getPolymorphicElement方法,实现minusKey方法时,就需要通过minusPolymorphicKey方法,代码如下:

  1. public fun <E : Element> Element.getPolymorphicElement(key: Key<E>): E? {
  2. // 如果key是AbstractCoroutineContextKey
  3. if (key is AbstractCoroutineContextKey<*, *>) {
  4. // 如果key是当前key的子key,则基类强制转换成衍生类,并返回
  5. @Suppress("UNCHECKED_CAST")
  6. return if (key.isSubKey(this.key)) key.tryCast(this) as? E else null
  7. }
  8. // 如果key不是AbstractCoroutineContextKey
  9. // 如果key相等,则强制转换,并返回
  10. @Suppress("UNCHECKED_CAST")
  11. return if (this.key === key) this as E else null
  12. }
  1. public fun Element.minusPolymorphicKey(key: Key<*>): CoroutineContext {
  2. // 如果key是AbstractCoroutineContextKey
  3. if (key is AbstractCoroutineContextKey<*, *>) {
  4. // 如果key是当前key的子key,基类强制转换后不为空,说明当前Element需要去掉,因此返回空上下文,否则返回自身
  5. return if (key.isSubKey(this.key) && key.tryCast(this) != null) EmptyCoroutineContext else this
  6. }
  7. // 如果key不是AbstractCoroutineContextKey
  8. // 如果key相等,说明当前Element需要去掉,因此返回空上下文,否则返回自身
  9. return if (this.key === key) EmptyCoroutineContext else this
  10. }

到此这篇关于Kotlin协程上下文与上下文元素深入理解的文章就介绍到这了,更多相关Kotlin上下文内容请搜索w3xue以前的文章或继续浏览下面的相关文章希望大家以后多多支持w3xue!

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号