Kotlin协程教程(2):协程做用域与各类builder们

做用域与上下文

协程做用域本质是一个接口,既然是一个接口,那么它就能够被某个类去实现(implement),实现它的那个类,也就具有了一些能力。数据库

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

那么它具有了哪些能力呢?网络

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

好比咱们有一个界面,里面有一些数据是须要经过网络或者文件或者数据库才能获取的,咱们想经过协程去获取它们,但因为界面可能随时会被关闭,咱们但愿界面关闭的时候,协程就不要再去工做了。async

咱们能够这样写ui

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

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

    fun destroy() {
        (this as CoroutineScope).cancel()
    }
}

fun main() {

    val myClass = MyClass()

    // 由于myClass已是一个CoroutineScope对象了,固然也能够经过这种方式来启动协程
    myClass.launch {
        for (i in 0..10) {
            println("MyClass launch1 $i *****")
            delay(100)
        }
    }

    myClass.doWork()

    Thread.sleep(500) // 让协程工做一会

    myClass.destroy() // myClass须要被回收了!

    Thread.sleep(500) // 等一会方便观察输出
}

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

如今来小小的回顾下上面说的,协程必需要在CoroutineScope中才能启动,本质是launch和async是CoroutineScope的扩展方法,在一个协程做用域CoroutineScope中启动的协程,都将受到这个做用域的管控,能够经过这个做用域的对象来取消内部的全部协程。spa

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

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

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

clipboard.png

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

GlobalScope.launch {
    println("GlobalScope ${this.toString()}")
    launch {
        println("A ${this.toString()}")
        launch {
            println("A1 ${this.toString()}")
        }
    }

    launch {
        println("B ${this.toString()}")
    }
}

运行结果:

GlobalScope StandaloneCoroutine{Active}@714834a4
B StandaloneCoroutine{Active}@6be16ee2
A StandaloneCoroutine{Active}@6a716a81
A1 StandaloneCoroutine{Active}@64b699bf

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

clipboard.png

默认状况下,每一个协程都要等待它的子协程所有完成后,才能结束本身。这种形式,就被称为结构化的并发

各类builder们

关于GlobalScope.launch,还有一个小特性,就是它更像一个守护线程,没法使进程保活。具体来讲,就是,若是进程中只有这样一个守护线程,可能会被干掉。

fun main()  {
    runBlocking {
        launch {
            for (i in 0 ..100) {
                delay(1000)
                println("hahaha")
            }
        }
    }
    
    println("qqqqqqqqqqqq")
}

上面的代码,能够正确的输出一堆hahahah,最终会输出qqqqqqqq。

但若是将launch换成GlobalScope.launch,就是另外一种效果了

fun main()  {
    runBlocking {
        GlobalScope.launch { // 注意这里的变化
            for (i in 0 ..100) {
                delay(1000)
                println("hahaha")
            }
        }
    }

    println("qqqqqqqqqqqq")
}

运行结果为:
直接输出qqqqqqqqqqqqq,进程就结束了。

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

clipboard.png

总结

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

新启动的协程又会建立本身的做用域,能够自由的组合和包含,外层的协程必需要等到内部的协程所有完成了,才能完成本身的,这即是结构化的并发。

协程做用域其实是绑定了一个Job对象,这个Job对象表示做用域内全部协程的执行单元,能够经过这个Job对象取消内部的协程。

若是你喜欢这篇文章,欢迎点赞评论打赏
更多干货内容,欢迎关注个人公众号:好奇码农君

相关文章
相关标签/搜索