Kotlin 协程异常处理

1.引言

kotlin的异常处理很不容易理解,看了好久,好久慢慢才明白,因为有必要写一篇文章搞清楚一下问题:

  • try-catch捕获异常
  • CoroutineExceptionHandler
  • supervisorScope 和SupervisorJob

2.正题

先看看这个例子:

    //协程异常处理  1.在协程作用域中捕捉  2.协程异常处理器
    fun coroutineBuildRunBolck7() {
        try {
            Thread() {
                throw NullPointerException()
            }.start()
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

结果:运行崩溃
try-catch 只能捕捉当前线程的堆栈信息。对于非当前线程无法实现捕捉 。既然这样下面代码应该会被捕捉到:

fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
        try {
            launch {
                throw NullPointerException()
            }
        } catch (e: Exception) {
            e.printStackTrace()
            Log.d("wangxuyang", "" + e.message)
        }
    }

结果:运行崩溃。此时这里应该有一个大大的问号?为什么会这样?
结论:launch启动的协程,是不会传播异常的。
此时肯定会问什么叫传播异常?
所谓的传播异常,是指能够将异常主动往外抛到启动顶层协程所在的线程。因为launch启动的协程,是不会将异常抛到线程,所以try-catch无法捕捉.

为了让这种异常能够捕捉到。协程引入了CoroutineExceptionHandler

2.2 CoroutineExceptionHandler

CoroutineExceptionHandler是协程用来解决launch启动协程,异常不传播的问题。当出现这种异常不传播问题。协程内部是如何处理的呢?

当一个协程发生了异常,它将把异常传播给它的父协程,父协程会做以下几件事:

  • 取消其他子协程
  • 取消自己
  • 将异常传播给自己的父协程
    异常最终将传播至继承结构的根部。通过该 CoroutineScope 创建的所有协程都将被取消。

//插入一张图片

让CoroutineExceptionHandler 拦截异常不传播,需要满足以下条件:
何时 :是被可以自动抛异常的协程抛出的(launch,而不是 async)
何地 :GlobalScope全局作用域.launch 启动一个根协程,或者自定义一个

所以由上可以得知。要想解决上述的异常。我们得在根协程中做异常处理。

 val handler = CoroutineExceptionHandler { _, exception ->
        Log.d("wangxuyang", "CoroutineExceptionHandler")
    }
 fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
        //方式1
        GlobalScope.launch(handler) {
            launch {
                throw NullPointerException()
            }
        }
        //方式2,
        CoroutineScope(Job()+handler)
            .launch {
                throw NullPointerException()
            }
    }

结果:打印2021-12-26 17:08:32.332 14031-14278/com.heytap.tv.myapplication D/wangxuyang: CoroutineExceptionHandler

再看一个案例:

fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
        GlobalScope.launch {
            launch(handler) {
                launch {
                    throw NullPointerException()
                }
            }
        }
    }

这种依旧会崩溃。它不等同于那样try-catch到了就停止上报。协程中的传播异常,最终都会上报到根协程中。中途无法处理异常。所以上面依旧不行

2.3 supervisorScope 和 SupervisorJob

上面提到,当一个协程报异常了,会终止它的兄弟姐妹协程。这样很不合理。为此kotlin 协程开发团队提供了一个supervisorScope方法。用来解决这个问题。使用它,当子协程出现异常,不会影响其兄弟姐妹的协程运行

val handler = CoroutineExceptionHandler { _, exception ->
        Log.d("wangxuyang", "CoroutineExceptionHandler")
    }
    //协程异常处理  1.在协程作用域中捕捉  2.协程异常处理器
    fun coroutineBuildRunBolck7() = runBlocking(Dispatchers.IO) {
        CoroutineScope(Job() + handler)
            .launch {
                supervisorScope {
                    launch {
                        Log.d("wangxuyang", "start job1 delay")
                        delay(1000)
                        Log.d("wangxuyang", "end job1 delay")
                    }
                    launch {
                        Log.d("wangxuyang", "job2 throw execption")
                        throw NullPointerException()
                    }
                }
            }
    }

输出结果:
D/wangxuyang: start job1 delay
D/wangxuyang:job2 throw execption
D/wangxuyang:CoroutineExceptionHandler
D/wangxuyang:end job1 delay

上面的例子可以使用SupervisorJob 达到同样的效果。supervisorScope ==SupervisorJob .

 //协程异常处理  1.在协程作用域中捕捉  2.协程异常处理器
val handler = CoroutineExceptionHandler { _, exception ->
    Log.d("wangxuyang", "CoroutineExceptionHandler")
}
with(CoroutineScope(SupervisorJob() + handler)) {
            launch {
                Log.d("wangxuyang", "start job1 delay")
                delay(1000)
                Log.d("wangxuyang", "end job1 delay")
            }
            launch {
                Log.d("wangxuyang", "job2 throw execption")
                throw NullPointerException()
            }
        }

以上就是我对协程异常处理的理解

你可能感兴趣的:(Kotlin 协程异常处理)