文章目录

  • ​​一、异常传播的特殊情况​​
  • ​​1、取消子协程示例​​
  • ​​2、子协程抛出异常后父协程处理异常时机示例​​
  • ​​二、异常聚合 ( 多个子协程抛出的异常会聚合到第一个异常中 )​​






一、异常传播的特殊情况



在 ​​【Kotlin 协程】协程异常处理 ① ( 根协程异常处理 | 自动传播异常 | 在协程体捕获异常 | 向用户暴露异常 | 在 await 处捕获异常 | 非根协程异常处理 | 异常传播特性 )​​ 博客中介绍到 协程 运行时 , 产生异常 , 会将异常 传递给 父协程 , 父协程会执行如下操作 :

  • ① 取消子协程 : 不仅仅取消产生异常的子协程 , 该父协程下所有的子协程都会取消 ;
  • ② 取消父协程 : 将父协程本身取消 ;
  • ③ 向父协程的父协程传播异常 : 继续将异常传播给 父协程的父协程 ;


但是也有特殊情况 :

  • 协程 调用 Job#cancel() 函数 进行取消操作时 , 会 抛出 CancellationException 异常
  • 如果 抛出 CancellationException 异常 取消 子协程 , 其 父协程 不会受其影响 ;
  • 如果 子协程 抛出的是 其它异常 , 该异常会被传递给 父协程 进行处理 ;
  • 如果 父协程 有多个子协程 , 多个子协程 都抛出异常 , 父协程会等到 所有子协程 都执行完毕会后 , 再处理 异常 ;


1、取消子协程示例



在下面的代码中 , 在 父协程中 使用 launch 协程构建器 创建了子协程 , 注意 如果想要子协程运行 , 必须在创建完子协程后 调用 ​​yield()​​ 函数 , 让 父协程 让渡线程执行权 , 也就是令 子协程 执行起来

子协程执行起来后 , 取消子协程 , 此时 在子协程中 , 会抛出 CancellationException 异常 , 该异常不会传递到 父协程 中 , 父协程 正常执行到结束 ;



代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.yield

class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

runBlocking {

// 父协程
val job = launch {
Log.i(TAG, "父协程开始执行")

// 子协程
val childJob = launch {
Log.i(TAG, "子协程执行开始")
try {
delay(200)
}finally {
Log.i(TAG, "子协程执行 finally 代码")
}
}

// 让渡协程的执行权, 让子协程执行
yield()

Log.i(TAG, "取消子协程")
childJob.cancel()

Log.i(TAG, "父协程执行完毕")
}

// 等待父协程执行完毕
job.join()
}
}
}

执行结果 :

23:38:43.639  I  父协程开始执行
23:38:43.640 I 子协程执行开始
23:38:43.642 I 取消子协程
23:38:43.643 I 父协程执行完毕
23:38:43.643 I 子协程执行 finally 代码

【Kotlin 协程】协程异常处理 ⑤ ( 异常传播的特殊情况 | 取消子协程示例 | 子协程抛出异常后父协程处理异常时机示例 | 异常聚合 | 多个子协程抛出的异常会聚合到第一个异常中 )_kotlin



2、子协程抛出异常后父协程处理异常时机示例



父协程 中 使用 launch 创建了 2 个 子协程 ,

  • 子协程 1 执行 2 秒后 , 在 finally 中再执行 1 秒 ;
  • 子协程 2 执行 100 ms 后 , 自动抛出异常 ;

在 子协程 2 抛出异常后 , 两个子协程 都会退出 , 但是 子协程 1 的 finally 代码要执行 1000 ms , 这里父协程 等待 子协程 1 执行完毕后 , 才会处理 子协程 抛出的异常 ;



代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

runBlocking {
// 创建 协程异常处理器 CoroutineExceptionHandler
val coroutineExceptionHandler = CoroutineExceptionHandler {
coroutineContext, throwable ->

Log.i(TAG, "CoroutineExceptionHandler 中处理异常 " +
"\n协程上下文 ${coroutineContext}" +
"\n异常内容 ${throwable}")
}

// 父协程
val job = GlobalScope.launch(coroutineExceptionHandler) {
Log.i(TAG, "父协程开始执行")

// 子协程 1
val childJob1 = launch {
Log.i(TAG, "子协程 1 执行开始")
try {
delay(2000)
}finally {
withContext(NonCancellable){
Log.i(TAG, "子协程 1 执行 finally 代码")
delay(1000)
Log.i(TAG, "子协程 1 执行 finally 代码结束")
}
}
}

// 子协程 2
val childJob2 = launch {
Log.i(TAG, "子协程 2 执行开始")
delay(100)
Log.i(TAG, "子协程 2 抛出 IllegalArgumentException 异常")
throw IllegalArgumentException()
}

// 运行时 子协程 2 会先抛出异常 , 此时 子协程 1 也会被取消
// 父协程 会在 两个协程都取消后 才会处理异常
}

// 等待父协程执行完毕
job.join()
Log.i(TAG, "父协程执行完毕")
}
}
}

执行结果 : 由下面的日志可知 ,

  • 子协程 1 没有执行完 2 秒 , 就被 子协程 2 的异常打断了 ,
  • 但是 子协程 1 中的 finally 代码中的 1 秒执行完毕了 ;
  • 子协程 2 早早抛出异常退出了 , 子协程 1 还执行了 1 秒 ,
  • 最后 父协程 等 子协程 1 执行完毕后 , 才处理的 子协程 2 抛出的 异常 ;
00:07:35.258  I  父协程开始执行
00:07:35.262 I 子协程 1 执行开始
00:07:35.270 I 子协程 2 执行开始
00:07:35.427 I 子协程 2 抛出 IllegalArgumentException 异常
00:07:35.467 I 子协程 1 执行 finally 代码
00:07:36.484 I 子协程 1 执行 finally 代码结束
00:07:36.504 I CoroutineExceptionHandler 中处理异常
协程上下文 [kim.hsl.coroutine.MainActivity$onCreate$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@f30fe8, StandaloneCoroutine{Cancelling}@bc6a601, Dispatchers.Default]
异常内容 java.lang.IllegalArgumentException
00:07:36.516 I 父协程执行完毕

【Kotlin 协程】协程异常处理 ⑤ ( 异常传播的特殊情况 | 取消子协程示例 | 子协程抛出异常后父协程处理异常时机示例 | 异常聚合 | 多个子协程抛出的异常会聚合到第一个异常中 )_kotlin_02






二、异常聚合 ( 多个子协程抛出的异常会聚合到第一个异常中 )



父协程 中 有多个 子协程 , 这些子协程 都 抛出了 异常 , 此时 只会处理 第一个 异常 ;

这是因为 多个 子协程 , 如果出现了多个异常 , 从第二个异常开始 , 都会将异常绑定到第一个异常上面 ;



在 CoroutineExceptionHandler 中 , 调用 throwable.suppressed.contentToString() 可以获取多个异常 , 被绑定的异常会存放到一个数组中 , 有多少个异常都会显示出来 ;



代码示例 :

package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity() {
val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

runBlocking {
// 创建 协程异常处理器 CoroutineExceptionHandler
val coroutineExceptionHandler = CoroutineExceptionHandler {
coroutineContext, throwable ->

Log.i(TAG, "CoroutineExceptionHandler 中处理异常 " +
"\n协程上下文 ${coroutineContext}" +
"\n异常内容 ${throwable} " +
// 这是一个数组 , 不管有多少个异常 , 都会打印出来
"\n第二个异常内容 ${throwable.suppressed.contentToString()}")
}

// 父协程
val job = GlobalScope.launch(coroutineExceptionHandler) {
Log.i(TAG, "父协程开始执行")

// 子协程 1
val childJob1 = launch {
Log.i(TAG, "子协程 1 执行开始")
try {
delay(2000)
}finally {
Log.i(TAG, "子协程 1 抛出 IllegalArgumentException 异常 ( 第二个异常 )")
throw IllegalArgumentException()
}
}

// 子协程 2
val childJob2 = launch {
Log.i(TAG, "子协程 2 执行开始")
delay(100)
Log.i(TAG, "子协程 2 抛出 ArithmeticException 异常 ( 第一个异常 )")
throw ArithmeticException()
}

// 运行时 子协程 2 会先抛出异常 , 此时 子协程 1 也会被取消 , 在 finally 中抛出异常
// 父协程 会在 两个协程都取消后 才会处理异常
// 第二个异常 会被 绑定到 第一个异常 上
}

// 等待父协程执行完毕
job.join()
Log.i(TAG, "父协程执行完毕")
}
}
}

执行结果 :

00:46:21.239  I  父协程开始执行
00:46:21.243 I 子协程 1 执行开始
00:46:21.245 I 子协程 2 执行开始
00:46:21.387 I 子协程 2 抛出 ArithmeticException 异常 ( 第一个异常 )
00:46:21.390 I 子协程 1 抛出 IllegalArgumentException 异常 ( 第二个异常 )
00:46:21.490 I CoroutineExceptionHandler 中处理异常
协程上下文 [kim.hsl.coroutine.MainActivity$onCreate$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@bc6a601, StandaloneCoroutine{Cancelling}@fef2ca6, Dispatchers.Default]
异常内容 java.lang.ArithmeticException
第二个异常内容 [java.lang.IllegalArgumentException]
00:46:21.492 I 父协程执行完毕

【Kotlin 协程】协程异常处理 ⑤ ( 异常传播的特殊情况 | 取消子协程示例 | 子协程抛出异常后父协程处理异常时机示例 | 异常聚合 | 多个子协程抛出的异常会聚合到第一个异常中 )_android_03