Kotlin-作用域与上下文

协程上下文

在协程的源代码中协程的上下文是一个CoroutineContext接口,他就是一个存储实现了CoroutineContext接口的元素的集合,也就是协程上下文是各种不同元素的集合。其中主元素是协程中的 Job,Dispatcher和,ContinuationInterceptor等等的元素。

  • launch启动一个协程

默认启动通过launch启动一个协程的时候包含一个继承自作用域的CoroutineContext,和一个默认的启动模式,调度器和要执行的协程体,之后返回一个Job

此处说明一下调度器Dispatcher就是一个CoroutineDispatcher它实现了ContinuationInterceptor这个接口通过内部的interceptContinuation获取一个Continuation对象,通过resume和resumeWithException对协程体内部的执行的成功与否进行处理

Kotlin协程- 作用域

在协程的源代码中有一个接口 CoroutineScope用来指定协程的作用域

CoroutineContext:协程的上下文
MainScope:实现了 CoroutineScope接口 同时是通过调度器调度到了主线程的协程作用域
GlobalScope:实现了 CoroutineScope接口 同时执行了一个空的上下文对象的协程作用域
coroutineContext:通过这个方法可以在一个协程中启动协程是承袭他的上下文,同时内部的job将成为外部job 的子job,当一个父协程被取消的时候,所有它的子协程也会被递归的取消。
CoroutineScope(coroutineContext:CoroutineContext):通过传递一个协程上下文实现作用域的创建

在协程的源代码中协程的上下文是一个CoroutineContext接口,他就是一个存储实现了CoroutineContext接口的元素的集合,也就是协程上下文是各种不同元素的集合。其中主元素是协程中的 Job,Dispatcher和,ContinuationInterceptor等等的元素。


CoroutineScope 源码

package kotlinx.coroutines

import kotlinx.coroutines.internal.*
import kotlinx.coroutines.intrinsics.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*

public interface CoroutineScope {
    
    public val coroutineContext: CoroutineContext
}

public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
    ContextScope(coroutineContext + context)

@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
public val CoroutineScope.isActive: Boolean
    get() = coroutineContext[Job]?.isActive ?: true

public object GlobalScope : CoroutineScope {
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val coroutine = ScopeCoroutine(uCont.context, uCont)
        coroutine.startUndispatchedOrReturn(coroutine, block)
    }

@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

public fun CoroutineScope.cancel(cause: CancellationException? = null) {
    val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
    job.cancel(cause)
}

public fun CoroutineScope.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))

public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()

协程作用域本质是一个接口,既然是一个接口,那么它就可以被某个类去实现(implement),实现它的那个类,也就具备了一些能力。

class MyClass: CoroutineScope {
    // MyClass就具备了CoroutineScope的一些能力
}

那么它具备了哪些能力呢?

当然是启动协程的能力和停止协程的能力。除了runBlocking有一些特殊外,launch和async其实都是CoroutineScope的扩展方法,它们两个都必须通过作用域才能调用。

比如我们有一个界面,里面有一些数据是需要通过网络或者文件或者数据库才能获取的,我们想通过协程去获取它们,但由于界面可能随时会被关闭,我们希望界面关闭的时候,协程就不要再去工作了。

我们可以这样写

class MyClass: CoroutineScope by CoroutineScope(Dispatchers.Default) {

    fun doWork() {
        launch {
            for (i in 0..100) {
                println("MyClass launch1 $i -----")
                delay(100)
            }
        }
    }

    fun destroy() {
        println("协程要停止了")
        (this as CoroutineScope).cancel()
    }
}

fun main() {

    val myClass = MyClass()

    // 因为myClass已经是一个CoroutineScope对象了,当然也可以通过这种方式来启动协程
    myClass.launch {
        for (i in 0..100) {
            println("MyClass launch1 $i *****")
            delay(100)
        }
    }

    myClass.doWork()
    Thread.sleep(500) // 让协程工作一会
    myClass.destroy() // myClass需要被回收了!
    Thread.sleep(500) // 等一会方便观察输出
}

当destroy被调用的时候,myClass的协程就都停止工作了,是不是很爽,很方便。这个设计将非常适合与在GUI程序上使用。

现在来小小的回顾下上面说的,协程必须要在CoroutineScope中才能启动,本质是launch和async是CoroutineScope的扩展方法,在一个协程作用域CoroutineScope中启动的协程,都将受到这个作用域的管控,可以通过这个作用域的对象来取消内部的所有协程。


上下文

CoroutineContext 源码

package kotlin.coroutines

@SinceKotlin("1.3")
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 // 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)
                    }
                }
            }

    public fun minusKey(key: Key<*>): CoroutineContext

    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

        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
    }
}

协程作用域CoroutineScope的内部,又包含了一个协程上下文(CoroutineContext) 对象。

协程上下文对象中,是一个key-value的集合,其中,最重要的一个元素就是Job,它表示了当前上下文对应的协程执行单元。

它们的关系看起来就像是这样的:


launch 源码

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

async 源码

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

另外,launch和async启动后的协程,也是一个新的作用域,如下代码,我构造了好几个协程,并print出当前的Scope对象。

suspend fun main(){
    GlobalScope.launch {
        println("GlobalScope ${this.toString()}")
        launch {
            println("A ${this.toString()}")
            launch {
                println("A1 ${this.toString()}")
            }
        }
        launch {
            println("B ${this.toString()}")
        }
    }
    delay(10000)
}

运行结果:

GlobalScope StandaloneCoroutine{Active}@2d5a56c1
A StandaloneCoroutine{Active}@14e52dfc
B StandaloneCoroutine{Active}@21a66372
A1 StandaloneCoroutine{Active}@3b302464

可见,作用域启动新协程也是一个新的作用域,它们的关系可以并列,也可以包含,组成了一个作用域的树形结构。

默认情况下,每个协程都要等待它的子协程全部完成后,才能结束自己。这种形式,就被称为结构化的并发


各种builder们

在官方文档上,launch、async被称为coroutine builder,我想不严谨的扩大一下这个概念,将经常使用到的都成为builder,我已经总结了它们的特性,列在下面的表格中:


总结

协程作用域本质是一个接口,我们可以手动声明这样一个接口,也可以让一个类实现这个接口。在语义上,仿佛就像定义了一个作用域,但又巧妙的在这个作用域的范围内,可以使用启动协程的方法了,启动的协程也自然的绑定在这个作用域上。

新启动的协程又会创建自己的作用域,可以自由的组合和包含,外层的协程必须要等到内部的协程全部完成了,才能完成自己的,这便是结构化的并发。

协程作用域实际上是绑定了一个Job对象,这个Job对象表示作用域内所有协程的执行单元,可以通过这个Job对象取消内部的协程。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259