【Kotlin 协程】协程异常处理 ③ ( 协程异常处理器 CoroutineExceptionHandler 捕获异常 | 验证 CoroutineScope 协程的异常捕捉示例 )

2023-03-30 18:25:35 浏览数 (1)

文章目录

  • 一、协程异常处理器 CoroutineExceptionHandler 捕获异常
    • 1、对比 launch 和 async 创建的协程的异常捕捉示例
    • 2、验证 CoroutineScope 协程的异常捕捉示例

一、协程异常处理器 CoroutineExceptionHandler 捕获异常


在 【Kotlin 协程】协程上下文 ( 协程上下文构成要素 | 指定协程上下文元素组合 | 协程上下文元素的继承关系 | 协程上下文元素的几种指定形式 | 默认 | 继承 | 自定义指定 ) 博客中 , 介绍了 协程上下文 CoroutineContext 组成要素 , 其中包含了 协程异常处理器 CoroutineExceptionHandler , 用于 在协程中捕获异常 ;

异常捕获 : 在协程中 , 使用 CoroutineExceptionHandler 对协程运行过程中产生的 异常 进行捕获 , 异常满足如下两个条件才会被捕 :

  • 异常捕获时机 : 协程 自动抛出 的异常 , 可以在协程内被捕获 ; 使用 launch 构建的协程 可以在协程中捕获异常 , 使用 async 构建的协程 在 await 处捕获异常 ;
  • 异常捕获位置 : 在 协程作用域 CoroutineScope 或者在 根协程 中 捕获 异常 ;

1、对比 launch 和 async 创建的协程的异常捕捉示例

代码示例 :

  • 使用 launch 构造的协程 , 可以使用 CoroutineExceptionHandler 捕获异常 ;
  • 使用 async 构造的协程 , 无法使用 CoroutineExceptionHandler 捕获异常 , 异常直接抛出 , 导致程序崩溃 ;
代码语言:javascript复制
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<Unit> {
            // 创建 协程异常处理器 CoroutineExceptionHandler
            val coroutineExceptionHandler = CoroutineExceptionHandler {
                    coroutineContext, throwable ->

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

            // 创建协程 , 传入 CoroutineExceptionHandler 作为协程上下文参数
            val job = GlobalScope.launch(coroutineExceptionHandler) {
                // 该异常会被捕获
                throw AssertionError()
            }

            // 创建协程 , 传入 CoroutineExceptionHandler 作为协程上下文参数
            val deferred = GlobalScope.async(coroutineExceptionHandler) {
                // 该异常不会被捕获
                throw ArithmeticException()
            }

            // 等待 job 执行完毕
            job.join()
            // 等待 deferred 执行完毕
            deferred.await()
        }
    }
}

执行结果 : 捕获到了 launch 创建的协程中的异常 , 但是 async 创建的协程中的异常直接抛出导致程序崩溃 ;

代码语言:javascript复制
14:35:22.587  I  CoroutineExceptionHandler 中处理异常 
                 协程上下文 [kim.hsl.coroutine.MainActivity$onCreate$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@f30fe8, StandaloneCoroutine{Cancelling}@bc6a601, Dispatchers.Default]
                 异常内容 java.lang.AssertionError
14:35:22.591  D  Shutting down VM
14:35:22.595  E  FATAL EXCEPTION: main
                 Process: kim.hsl.coroutine, PID: 30617
                 java.lang.RuntimeException: Unable to start activity ComponentInfo{kim.hsl.coroutine/kim.hsl.coroutine.MainActivity}: java.lang.ArithmeticException
                 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2951)
                 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3086)
                 	at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
                 	at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
                 	at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
                 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1816)
                 	at android.os.Handler.dispatchMessage(Handler.java:106)
                 	at android.os.Looper.loop(Looper.java:193)
                 	at android.app.ActivityThread.main(ActivityThread.java:6718)
                 	at java.lang.reflect.Method.invoke(Native Method)
                 	at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
                 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
                 Caused by: java.lang.ArithmeticException
                 	at kim.hsl.coroutine.MainActivity$onCreate$1$deferred$1.invokeSuspend(MainActivity.kt:33)
                 	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
                 	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
                 	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
                 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
                 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
                 	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
14:35:22.605  W    Force finishing activity kim.hsl.coroutine/.MainActivity
14:35:22.618  I  Sending signal. PID: 30617 SIG: 9
---------------------------- PROCESS ENDED (30617) for package kim.hsl.coroutine ----------------------------

2、验证 CoroutineScope 协程的异常捕捉示例

在使用 CoroutineExceptionHandler 对协程运行过程中产生的 异常 进行捕获 时 , 异常捕获的位置 只能是 协程作用域 CoroutineScope 或者在 根协程 中 ;

在上面的小节验证了 异常捕获位置 在根协程 中的情况 , 在本小节示例中 , 验证在 协程作用域 CoroutineScope 中捕获异常 ;

代码示例 : 在 协程作用域 中 , 使用 launch 协程构建器 创建协程 , 传入 CoroutineExceptionHandler 实例对象参数 , 在其中再创建子协程 , 抛出异常 , 最终可以捕获到在子协程中抛出的异常 ;

下面代码中 创建协程作用域 时 , 使用的 CoroutineScope(Job()) 进行创建 , 不是 SupervisorJob , 因此 在子协程中抛出的异常 , 会传递给父协程 , 由父协程处理异常 , 父协程创建时使用的 val job = scope.launch(coroutineExceptionHandler) 代码 , 在协程构建器中传入了 协程异常处理器 , 因此该协程异常处理器 可捕获 子协程传递给父协程的异常 ;

异常处理器 coroutineExceptionHandler , 必须安装给 根协程 , 不能给内部协程安装 ; 如果将 coroutineExceptionHandler 设置给 CoroutineScope(Job()) 创建的协程的子协程 , 则异常不会被捕获到 ;

代码语言:javascript复制
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<Unit> {
            // 创建 协程异常处理器 CoroutineExceptionHandler
            val coroutineExceptionHandler = CoroutineExceptionHandler {
                    coroutineContext, throwable ->

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

            // 验证 在 协程作用域 CoroutineScope 中的异常
            // 可以使用 CoroutineExceptionHandler 捕获
            val scope = CoroutineScope(Job())
            val job = scope.launch(coroutineExceptionHandler) {
                launch {
                    throw IllegalArgumentException()
                }
            }

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

执行结果 : 捕获到了在 CoroutineScope 创建的协程的子协程中抛出异常 ;

代码语言:javascript复制
15:03:27.654  I  CoroutineExceptionHandler 中处理异常 
                 协程上下文 [kim.hsl.coroutine.MainActivity$onCreate$1$invokeSuspend$$inlined$CoroutineExceptionHandler$1@f30fe8, StandaloneCoroutine{Cancelling}@bc6a601, Dispatchers.Default]
                 异常内容 java.lang.IllegalArgumentException
15:03:27.688  D  Skia GL Pipeline

0 人点赞