上下文
协程总是运行在一些以 CoroutineContext 类型为代表的上下文中
调度器
协程上下文包含一个 协程调度器 (参见 CoroutineDispatcher)它确定了相关的协程在哪个线程或哪些线程上执行。协程调度器可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。
所有的协程构建器诸如 launch 和 async 接收一个可选的 CoroutineContext 参数,它可以被用来显式的为一个新协程或其它上下文元素指定一个调度器。
以下是集中函数的上下文
- launch:运行在父协程的上下文中
- launch(Dispatchers.Unconfined)将不受限的运行的在主线程中
- launch(Dispatchers.Default) 运行在默认调度器中
- launch(newSingleThreadContext("MyOwnThread"))运行在一个新线程中
- GlobalScope.launch使用的是Dispatchers.Default调度器
非受限调度器
Dispatchers.Default就是非受限调度器。
非受限调度器,我的理解是它不限制协程在某个父协程中运行,也就是说协程在运行过程中可以改变所运行的协程,比如如下代码
launch(Dispatchers.Unconfined) { // 非受限的——将和主线程一起工作 println("Unconfined")//这行代码运行在主线程 delay(500) println("Unconfined")//运行在单独协程中,因为调用过delay挂起过,所以在此恢复的时候协程变运行在单独协程中了 } 复制代码
Dispatchers.Default最好不要出现在你的业务代码中,那将是麻烦的
子协程
当一个协程被其它协程在 CoroutineScope 中启动的时候, 它将通过 CoroutineScope.coroutineContext 来承袭上下文,并且这个新协程的 Job 将会成为父协程作业的 子 作业。当一个父协程被取消的时候,所有它的子协程也会被递归的取消。
当使用 GlobalScope 来启动一个协程时,则新协程的作业没有父作业。 因此它与这个启动的作用域无关且独立运作。
- 代码
fun main() = runBlocking { val job = launch {//这个就是我们要启动的父协程 GlobalScope.launch { log(A)//会被调用 delay(1000) log(B)//会被调用,因为这是顶级协程 } launch { log(C)//会被调用 delay(1000) log(D)//不会被调用,因为父协程被取消,子协程也会被取消 } } delay(500) job.cancel()//等待500ms后取消父协程,查看两个子协程的打印情况 delay(2000) log(E) } 复制代码
- 日志
日志: A 日志: C 日志: B 日志: E 复制代码
- 结论
父协程被取消的时候内部的子协程被成功取消了,GlobalScope顶级协程没有被取消
父协程
一个父协程总是等待所有的子协程执行结束。父协程并不显式的跟踪所有子协程的启动,并且不必使用 Job.join 在最后的时候等待它们
协程命名
使用CoroutineName("v1coroutine")可以给协程命名,这在调试协程打印日志的时候非常有用
val v1 = async(CoroutineName("v1coroutine")) { delay(500) log("Computing v1") 252 } 复制代码
组合上下文中的参数
有时我们需要在协程上下文中定义多个元素。我们可以使用 + 操作符来实现。 比如说,我们可以显式指定一个调度器来启动协程并且同时显式指定一个命名,
例如:Dispatchers.Default + CoroutineName("test")
launch(Dispatchers.Default + CoroutineName("test")) { println("I'm working in thread ${Thread.currentThread().name}") } 复制代码
协程作用域
我们通过创建一个 CoroutineScope 实例来管理协程的生命周期,并使它与 activity 的生命周期相关联。CoroutineScope 可以通过 CoroutineScope() 创建或者通过MainScope() 工厂函数。前者创建了一个通用作用域,而后者为使用 Dispatchers.Main 作为默认调度器的 UI 应用程序 创建作用域
比如我们再我们的Activity中创建了一个MainScope,只需要在onDestroy中cancel协程就可以完全避免内存泄露。
两种方式能创建作用域:
CoroutineScope(Dispatchers.Default) 和 MainScope() 复制代码
- 代码
fun main() = runBlocking { val scope=CoroutineScope(Dispatchers.Default) repeat(9){ scope.launch { delay(100L*it)//这里delay的秒数递增,0,100,200,300,400 log(A) } } log(B) delay(200) scope.cancel()//因为这里延迟了200ms后取消,所以上面repeat中创建的协程中延迟大于200ms的将无法打印日志A log(C) } 复制代码
- 日志
日志: A 日志: B 日志: A 日志: A 日志: C 复制代码
- 结论
自定义的作用域CoroutineScope被取消后它内部所有未执行完成的协程都会被取消。