下面的图代表了一个协程a的生命,就像一条从上至下的直线,它的生命只有100ms
当我们在a协程延迟函数100ms之前开启一个子协程b,b做了200ms的事情,如果不考虑调度消耗的时间,那么a协程的生命也会延长成200ms
代码验证下:
fun `test context life`() = runBlocking {
//定义一个作用域
val a = CoroutineScope(Dispatchers.Default)
val startTime = System.currentTimeMillis()
//协程a开启
val jobA = a.launch {
//子协程b开启
val jobB = launch {
delay(200)
}
delay(100)
}
//等待协程a结束
jobA.join()
val endTime = System.currentTimeMillis()
println(endTime - startTime)
}
fun main() {
`test context life`()
}
结果: 237
如果我们把子协程b增加到delay 300ms,那么结果也会相应的变为: 323
协程上下文有以下几项构成,它们都是实现了CoroutineContext.Element接口,有些是实现了AbstractCoroutineContextElement接口,而AbstractCoroutineContextElement继承CoroutineContext.Element接口 1.Job:控制协程的生命周期,也是我们能拿到操作协程任务的唯一对象 2.CoroutineDispatcher:就是之前介绍的调度器 3.CoroutineName:协程的名字,一般输出日志用的 4.CoroutineExceptionHandler:处理未捕获的异常
协程上下文实现了运算符重载,我们可以用+号来组合一个CoroutineContext的元素
CorountineScope:创建协程作用域,新起线程,观察源码,内部实际实例化的是ContextScope,ContextScope被internal修饰,内部使用,我们实例化不了
1.runBlocking:将主线程转变为协程,会阻塞主线程,实际上用的是一个EmptyCoroutineContext作为上下文,它是一个主线程的协程上下文,静态的全局变量,我们其实就可以理解成是主线程 2.GlobalScope:也是用的EmptyCoroutineContext 3.MainScope:使用ContextScope构造了新的上下文 4.coroutineScope:继承的父协程上下文,不能算是全新的协程 等等
有了这些概念后,接下来通过代码,再熟悉巩固下 例子1:
fun `test context life1`() = runBlocking {
//定义一个作用域
val a = CoroutineScope(Dispatchers.Default)
//协程a开启
val jobA = a.launch {
delay(100)
println("jobA finished")
}
println("main finished")
}
结果: main finished 由于a是一个根协程,全新的上下文,runBlocking 是主线程的协程上下文,所以当a开启任务时,不会阻塞主线程,当我们的进程都跑完了,jobA finished肯定不会打印了
例子2:
fun `test context life2`() = runBlocking {
//定义一个作用域
val a = CoroutineScope(Dispatchers.Default)
//协程a开启
val jobA = a.launch {
delay(100)
println("jobA finished")
}
jobA.join()
println("main finished")
}
结果: jobA finished main finished 我们在主协程(主线程的协程)中,手动调用jobA的join方法,那么主线程就会阻塞,直到jobA执行完毕。这个和我们的多线程操作是一样的,主线程等待A线程执行完后再往后执行
例子3:
fun `test context life3`() = runBlocking {
launch {
delay(100)
println("jobA finished")
}
println("main finished")
}
结果: main finished jobA finished 这回我们没有构建新的协程作用域,而是在根协程中直接使用子协程的方式,当然了,协程的上下文继承关系,使得我们的主协程等待子协程执行完毕后才结束生命
例子4:
fun `test context life4`() = runBlocking {
launch(Dispatchers.IO + CoroutineName("jobA")) {
delay(100)
println("${coroutineContext[CoroutineName]} finished")
}
println("main finished")
}
结果: main finished CoroutineName(jobA) finished 即使我们指定了子协程的调度器和协程名,也不会影响协程上下文继承关系,主协程还是会等待子协程执行完毕后才结束生命
例子1:
fun `test coroutineScope exception1`() = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
delay(3000)
println("finished")
}
结果:
job2 finished
Exception in thread "main" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception1$1$job2$1.invokeSuspend(exceptionTest.kt:46)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.ResumeModeKt.resumeMode(ResumeMode.kt:67)
at kotlinx.coroutines.DispatchedKt.resume(Dispatched.kt:309)
at kotlinx.coroutines.DispatchedKt.dispatch(Dispatched.kt:298)
at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:250)
at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:260)
at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:332)
at kotlinx.coroutines.EventLoopImplBase$DelayedResumeTask.run(EventLoop.kt:298)
at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.kt:116)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:80)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:54)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:36)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt.test coroutineScope exception1(exceptionTest.kt:37)
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt.main(exceptionTest.kt:54)
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt.main(exceptionTest.kt)
Process finished with exit code 1
job2 1000ms后就发生了异常,导致job1和父协程都直接退出
例子1:
fun `test coroutineScope exception2`() = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = CoroutineScope(Dispatchers.IO).launch{
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
println("new CoroutineScope finished")
}
delay(3000)
println("finished")
}
结果:
job2 finished
Exception in thread "DefaultDispatcher-worker-2" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception1$1$job2$1.invokeSuspend(exceptionTest.kt:46)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
job finished
finished
Process finished with exit code 0
可以看出不同根协程的协程之间,异常并不会自动传递,我们的主线程上下文协程正常执行
再看例子2:
fun `test coroutineScope exception3`() = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = CoroutineScope(Dispatchers.IO).async{
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
println("new CoroutineScope finished")
}
delay(3000)
println("finished")
}
结果: job2 finished job finished finished
和例子1的唯一区别是,使用了全新上下文的协程使用了async启动,哈哈,这就奇怪了,为什么会这样?
还记得async启动的协程返回的是一个Deferred么,它可以使用await函数,来获取协程运行结果。那么试想一下,如果我就是想要一个协程执行完返回一个异常呢? 所以async中的异常会作为返回值,返回给调用await函数
fun `test coroutineScope exception4`() = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = CoroutineScope(Dispatchers.IO).async{
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
println("new CoroutineScope finished")
}
job2.await()
delay(3000)
println("finished")
}
结果:
job2 finished
Exception in thread "main" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception4$1$job2$1.invokeSuspend(exceptionTest.kt:96)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
Process finished with exit code 1
await的时候出现异常了,当然会导致协程退出,我们可以在await的时候捕获下这个异常,就不会影响主线程上下文的协程运行了
fun `test coroutineScope exception4`() = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = CoroutineScope(Dispatchers.IO).async {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
println("new CoroutineScope finished")
}
try {
job2.await()
} catch (e: Exception) {
e.printStackTrace()
}
delay(3000)
println("finished")
}
结果:
job2 finished
java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception4$1$job2$1.invokeSuspend(exceptionTest.kt:96)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
job finished
finished
Process finished with exit code 0
值得注意的是,同一继承关系下的协程使用await并无法捕获异常,还是会遵循第一条,导致整个协程生命周期结束
fun `test coroutineScope exception5`() = runBlocking {
val job2 = CoroutineScope(Dispatchers.IO).launch {
val job1 = launch {
delay(2000)
println("job finished")
}
val job3 = async {
delay(1000)
println("job3 finished")
throw IllegalArgumentException()
}
try {
job3.await()
} catch (e: Exception) {
e.printStackTrace()
}
delay(2000)
println("job2 finished")
}
job2.join()
println("finished")
}
结果:
job3 finished
java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception5$1$job2$1$job3$1.invokeSuspend(exceptionTest.kt:119)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
Exception in thread "DefaultDispatcher-worker-1" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test coroutineScope exception5$1$job2$1$job3$1.invokeSuspend(exceptionTest.kt:119)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
finished
Process finished with exit code 0
可以发现job3.await()的try catch并没有生效,所以向用户暴露异常只适用于不同上下文(没有继承关系)的协程
fun `test SupervisorJob exception`() = runBlocking {
val job1 = launch {
delay(2000)
println("job finished")
}
val job2 = async(SupervisorJob()) {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
delay(3000)
println("finished")
}
结果: job2 finished job finished finished 可以看到,job2的异常并没有影响其他继承关系的协程的执行
它使用有两个条件: 1.异常是自动抛出异常(launch) 2.实例化CoroutineScope的时候指定异常捕获器 或者 在一个根协程中
例子1:
fun `test SupervisorHandler exception1`() = runBlocking {
val handler = CoroutineExceptionHandler { _, throwable ->
println("caught: $throwable")
}
val scope = CoroutineScope(handler)
val job1 = scope.launch {
val job2 = launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
delay(2000)
println("job finished")
}
delay(4000)
println("finished")
}
结果: job2 finished caught: java.lang.IllegalArgumentException finished
job2抛出了异常,被捕获到了,但是scope的其他协程随之生命周期也都结束了
例子2:
fun `test SupervisorHandler exception2`() = runBlocking {
val handler = CoroutineExceptionHandler { _, throwable ->
println("caught: $throwable")
}
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch(handler) {
val job2 = launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
delay(2000)
println("job finished")
}
delay(4000)
println("finished")
}
结果: job2 finished caught: java.lang.IllegalArgumentException finished 和例子1相同,因为我们handler指定在了根协程
例子3:
fun `test SupervisorHandler exception3`() = runBlocking {
val handler = CoroutineExceptionHandler { _, throwable ->
println("caught: $throwable")
}
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch {
val job2 = launch(handler) {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
delay(2000)
println("job finished")
}
delay(4000)
println("finished")
}
结果:
job2 finished
Exception in thread "DefaultDispatcher-worker-4" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test SupervisorHandler exception$1$job1$1$job2$1.invokeSuspend(exceptionTest.kt:161)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
finished
Process finished with exit code 0
handler不是在根协程中,不能捕获
例子4:
fun `test SupervisorHandler exception4`() = runBlocking {
val handler = CoroutineExceptionHandler { _, throwable ->
println("caught: $throwable")
}
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch(handler) {
val job2 = launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
try {
job2.join()
}catch (e:Exception){
}
// val job3 = scope.launch {
// println("job3 finished")
// }
println("job delay")
delay(2000)
for(i in 0..10){
println(i)
}
println("job finished")
}
delay(4000)
println("finished")
}
结果: job2 finished job delay caught: java.lang.IllegalArgumentException finished
如果把scope根协程中的delay函数注释掉,会怎么样呢?
fun `test SupervisorHandler exception4`() = runBlocking {
val handler = CoroutineExceptionHandler { _, throwable ->
println("caught: $throwable")
}
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch(handler) {
val job2 = launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
try {
job2.join()
}catch (e:Exception){
}
// val job3 = scope.launch {
// println("job3 finished")
// }
println("job delay")
// delay(2000)
for(i in 0..10){
println(i)
}
println("job finished")
}
delay(4000)
println("finished")
}
结果: job2 finished job delay 0 1 2 3 4 5 6 7 8 9 10 job finished caught: java.lang.IllegalArgumentException
如果不包裹try catch 那么println("job delay")都不会执行
由例子4和例子5,我们可以推断,如果子协程有异常发生了,我们在等待时捕获异常后,根协程执行了挂起函数,那么它会直接中断,不执行挂起函数以下的代码,如果没有挂起函数,那么后面的代码还是会执行 为了加强验证这点,我们使用Thread.sleep(2000)替换delay函数测试下:
fun `test SupervisorHandler exception4`() = runBlocking {
val handler = CoroutineExceptionHandler { _, throwable ->
println("caught: $throwable")
}
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch(handler) {
val job2 = launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
try {
job2.join()
}catch (e:Exception){
}
// val job3 = scope.launch {
// println("job3 finished")
// }
println("job delay")
// delay(2000)
Thread.sleep(2000)
for(i in 0..10){
println(i)
}
println("job finished")
}
delay(4000)
println("finished")
}
结果还是和例子5一样: job2 finished job delay 0 1 2 3 4 5 6 7 8 9 10 job finished caught: java.lang.IllegalArgumentException finished
Process finished with exit code 0 其实出现这个情况,和我们之前取消协程是一样的,出现异常后会开始取消协程,但是CPU密集型的代码还会执行,但是遇到挂起函数就会抛一个CancellationException,导致协程结束运行,如果我们在挂起函数加上try catch打印,那么我们就可以看到CancellationException了
例子6,把job3的注释放开:
fun `test SupervisorHandler exception4`() = runBlocking {
val handler = CoroutineExceptionHandler { _, throwable ->
println("caught: $throwable")
}
val scope = CoroutineScope(Dispatchers.Default)
val job1 = scope.launch(handler) {
val job2 = launch {
delay(1000)
println("job2 finished")
throw IllegalArgumentException()
}
try {
job2.join()
}catch (e:Exception){
}
val job3 = scope.launch {
println("job3 finished")
}
println("job delay")
delay(2000)
// Thread.sleep(2000)
for(i in 0..10){
println(i)
}
println("job finished")
}
delay(4000)
println("finished")
}
结果:
job2 finished
job delay
caught: java.lang.IllegalArgumentException
Exception in thread "DefaultDispatcher-worker-1" java.lang.IllegalArgumentException
at com.aruba.mykotlinapplication.coroutine.ExceptionTestKt$test SupervisorHandler exception4$1$job1$1$job2$1.invokeSuspend(exceptionTest.kt:227)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:238)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:594)
at kotlinx.coroutines.scheduling.CoroutineScheduler.access$runSafely(CoroutineScheduler.kt:60)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:742)
finished
Process finished with exit code 0
显然有异常没有被捕获,很明显这个异常是调用job3时输出的,由此又可以推断出,如果在等待任务结束时,任务出现异常并且手动捕获异常后,再启动子协程时,也会抛出异常,并且不可捕获 注意:新版本kotlin已修复这个bug,不会抛出异常了