Kotlin 协程协程异常处理 ⑤ ( 异常传播的特殊情况 | 取消子协程示例 | 子协程抛出异常后父协程处理异常时机示例 | 异常聚合 | 多个子协程抛出的异常会聚合到第一个异常中 )
Posted 韩曙亮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 协程协程异常处理 ⑤ ( 异常传播的特殊情况 | 取消子协程示例 | 子协程抛出异常后父协程处理异常时机示例 | 异常聚合 | 多个子协程抛出的异常会聚合到第一个异常中 )相关的知识,希望对你有一定的参考价值。
文章目录
一、异常传播的特殊情况
在 【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 代码
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, StandaloneCoroutineCancelling@bc6a601, Dispatchers.Default]
异常内容 java.lang.IllegalArgumentException
00:07:36.516 I 父协程执行完毕
二、异常聚合 ( 多个子协程抛出的异常会聚合到第一个异常中 )
父协程 中 有多个 子协程 , 这些子协程 都 抛出了 异常 , 此时 只会处理 第一个 异常 ;
这是因为 多个 子协程 , 如果出现了多个异常 , 从第二个异常开始 , 都会将异常绑定到第一个异常上面 ;
在 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, StandaloneCoroutineCancelling@fef2ca6, Dispatchers.Default]
异常内容 java.lang.ArithmeticException
第二个异常内容 [java.lang.IllegalArgumentException]
00:46:21.492 I 父协程执行完毕
以上是关于Kotlin 协程协程异常处理 ⑤ ( 异常传播的特殊情况 | 取消子协程示例 | 子协程抛出异常后父协程处理异常时机示例 | 异常聚合 | 多个子协程抛出的异常会聚合到第一个异常中 )的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin 协程协程异常处理 ① ( 根协程异常处理 | 自动传播异常 | 在协程体捕获异常 | 向用户暴露异常 | 在 await 处捕获异常 | 非根协程异常处理 | 异常传播特性 )
Kotlin 协程协程异常处理 ① ( 根协程异常处理 | 自动传播异常 | 在协程体捕获异常 | 向用户暴露异常 | 在 await 处捕获异常 | 非根协程异常处理 | 异常传播特性 )
Kotlin 协程协程异常处理 ② ( SupervisorJob 协程 | supervisorScope 协程作用域构建器函数 )
Kotlin 协程协程异常处理 ② ( SupervisorJob 协程 | supervisorScope 协程作用域构建器函数 )
Kotlin 协程协程异常处理 ③ ( 协程异常处理器 CoroutineExceptionHandler 捕获异常 | 验证 CoroutineScope 协程的异常捕捉示例 )
Kotlin 协程协程异常处理 ③ ( 协程异常处理器 CoroutineExceptionHandler 捕获异常 | 验证 CoroutineScope 协程的异常捕捉示例 )