
在我个人的理解中,挂起、挂起函数指的是当前的函数可以被「搁置」,不影响主流程的运行
val job = GlobalScope.launch {
delay(5000)
println("the job is done")
}
// job.join()
println("hello world")

在以上的代码中,launch中的函数会立刻执行,但是需要等待5s。由于挂起函数有不影响主流程的特性,所以会先输出hello world,等到5s后再打印the job is done。
val job = GlobalScope.launch {
delay(5000)
println("the job is done")
}
job.join()
println("hello world") 但是在这里的代码中,由于join函数会先挂起当前的协程,等到job执行完成。当前的协程是什么? 当然是由runBlocking引导的协程,所以会先打印the job is done.
协程总是运行在一些以CoroutineContext类型为代表的上下文中。协程上下文在类型上更接近一个List。CoroutineContext包含一个调度器

CoroutineScope会跟踪它使用的launch和aysnc创建的所有协程。ViewModel有viewModelScope,LifeCycle有lifecycleScope。每一个协程构建器都是CoroutineScope的扩展函数,并且继承了其中的CoroutineContext。所以当使用CoroutineContext的协程构建器创建新的协程时,会继承CoroutineContext中的信息
class ExampleClass {
val scope = CoroutineScope(Job() + Dispatchers.Main)
fun exampleMethod() {
// Starts a new coroutine on Dispatchers.Main as it's the scope's default
val job1 = scope.launch {
// New coroutine with CoroutineName = "coroutine" (default)
}
// Starts a new coroutine on Dispatchers.Default
val job2 = scope.launch(Dispatchers.Default + "BackgroundCoroutine") {
// New coroutine with CoroutineName = "BackgroundCoroutine" (overridden)
}
}
} Job表示协程的句柄,可以通过这个类的实例来管理协程的生命周期
launch和async的对于异常的处理机制不同。当使用这两种构建器创建根协程时,launch会将异常视作未捕获异常,async需要对异常手动处理:
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = GlobalScope.launch { // launch 根协程
println("Throwing exception from launch")
throw IndexOutOfBoundsException() // 我们将在控制台打印 Thread.defaultUncaughtExceptionHandler
}
job.join()
println("Joined failed job")
val deferred = GlobalScope.async { // async 根协程
println("Throwing exception from async")
throw ArithmeticException() // 没有打印任何东西,依赖用户去调用等待
}
try {
deferred.await()
println("Unreached")
} catch (e: ArithmeticException) {
println("Caught ArithmeticException")
}
} 这里的打印行为是通过CoroutineExceptionHandler处理的。可以自定定义CoroutineExceptionHandler:
val exceptionHandler = CoroutineExceptionHandler {_, exception ->
println(exception.stackTrace)
}
val job2 = GlobalScope.launch(job + exceptionHandler){
} 在下面的代码中,父协程创建的实例是job,子协程创建的实例是child。使用的是Global.launch中调度的子协程,所以父子协程都具有相同的CoroutineContext。yield表示让出当前的协程调度器,给使用者同一个协程调度器的另外一个协程使用,这个协程就是child。但是很明显,这个让给子协程的动作好像只做了一下,如果是Dispatcher一直在子协程手中,则父协程不能有机会打印后面的内容。
val job = launch {
val child = launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("Child is cancelled")
}
}
yield()
println("Cancelling child")
child.cancel()
child.join()
yield()
println("Parent is not cancelled")
}
job.join() job取消主动会抛出异常CancellationException,这个异常会被异常handler处理,但是不会结束父协程,所以打印的结果是:

子协程的取消不会影响父协程的取消,但是如果子协程遇到了CancellationException之外的异常,异常会被ExceptionHandler处理,父协程也随之结束:
val r = GlobalScope.launch(exceptionHandler) {
val child = launch {
println(" child throw exception")
throw java.lang.ArithmeticException()
}
delay(Long.MAX_VALUE)
}
r.join() 输出:
child throw exception
exception: [Ljava.lang.StackTraceElement;@7db621c2
下面的代码展示了多个子协程的情况,如果有其中一个子协程抛出异常,父协程也会结束,其他的子协程也被迫关闭:
val root = GlobalScope.launch(exceptionHandler) {
val child1 = launch {
try{
delay(Long.MAX_VALUE)
}finally {
withContext(NonCancellable) {
println("Children are cancelled, but exception is not handled until all children terminate")
delay(100)
println("The first child finished its non cancellable block")
}
}
}
val child2 = launch { // 第二个子协程
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
delay(Long.MAX_VALUE)
}
root.join() 多个子协程出现的异常只会捕捉第一个。
如果取消父协程,子协程也会被取消吗?
// 父协程取消 子协程会被取消吗?
val root = GlobalScope.launch(exceptionHandler) {
val child = launch {
try{
delay(Long.MAX_VALUE)
println("ending of child: normally")
}finally {
println("ending of child: by parent")
}
}
delay(Long.MAX_VALUE)
}
println("cancel parent")
root.cancel()
delay(4000)
cancel parent
ending of child: by parent 所以总结一下父子协程的关系:
覆巢之下无完卵: 父协程取消 子协程也取消 而且是都要取消
子不孝 父之过: 子协程取消 父协程也要取消
所以是一种双向关系
firstChild被取消了,但是并不会让secondChild停止,同时父协程也没有被停止。
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
// 启动第一个子作业——这个示例将会忽略它的异常(不要在实践中这么做!)
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
println("The first child is failing")
throw AssertionError("The first child is cancelled")
}
// 启动第二个子作业
val secondChild = launch {
firstChild.join()
// 取消了第一个子作业且没有传播给第二个子作业
println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
try {
delay(Long.MAX_VALUE)
} finally {
// 但是取消了监督的传播
println("The second child is cancelled because the supervisor was cancelled")
}
}
// 等待直到第一个子作业失败且执行完成
firstChild.join()
println("Cancelling the supervisor")
supervisor.cancel()
secondChild.join()
} 我们可以使用supervisorScope实现类似的事情:
类似于SupervisoJob的单项传播
向coroutineScope一样,等到所有子协程结束之后结束自身
try {
supervisorScope {
val child = launch {
try {
println("The child is sleeping")
delay(Long.MAX_VALUE)
} finally {
println("The child is cancelled")
}
}
// 使用 yield 来给我们的子作业一个机会来执行打印
yield()
println("Throwing an exception from the scope")
throw AssertionError()
}
} catch(e: AssertionError) {
println("Caught an assertion error")
} 输出:
The child is sleeping
Throwing an exception from the scope
The child is cancelled
Caught an assertion error 协程构建器中都有一个默认的CoroutineContext (协程上下文)
协程上下文就像List一样,我们可以往里面添加Dispatcher Job CoroutineExceptionHandler 等等
所有的协程构建器都是CoroutineScope的扩展函数,而CoroutineContext则是它的一个属性。CoroutineScope指定协程构建器,同时将自身的CoroutineContext用来配置构建器的【上下文信息】(dispatcher exceptionhandler....)
子协程表示继承了父协程CoroutineScope的协程。普通的CoroutineScope(也是普通的CoroutineContext)中,异常传递是双向的。但是这种双向传递关系可以被打破,通过在CoroutineContext中设置SupervisorJob或者直接通过supervisorScope构建协程。