Kotlin 协程 supervisorScope {} 运行崩溃解决方法
作者:韩老九
Kotlin 协程 supervisorScope {} 运行崩溃解决
前言
简单介绍supervisorScope函数,它用于创建一个使用了 SupervisorJob 的 coroutineScope,
该作用域的特点:抛出的异常,不会 连锁取消 同级协程和父协程。
看过很多 supervisorScope {} 文档的使用,我照抄一摸一样的代码,运行就崩溃,最后找到了解决方法,应该是kotlin版本更新做过改动,当前我使用的是 androidx.core:core-ktx:1.9.0
解决方法
需要将CoroutineExceptionHandler,作为参数,才有效果,不然会崩溃。
private fun test() { // 原来的写法,现在会崩溃 // runBlocking { // Log.d("TAG", "Start") // launch { // delay(100) // Log.d("TAG", "Task from runBlocking") // } // supervisorScope { // val firstChild = launch { // Log.d("TAG", "First Child") // throw AssertionError("First child is cancelled") // } // val secondChild = launch { // Log.d("TAG", "Second Child") // } // Log.d("TAG", "Cancelling supervisor") // } // Log.d("TAG", "End") // } // 最新的写法 runBlocking { Log.d("TAG", "Start") launch { delay(100) Log.d("TAG", "Task from runBlocking") } supervisorScope { // 需要将CoroutineExceptionHandler,作为参数,才有效果,不然会崩溃 val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) { Log.d("TAG", "First Child") throw AssertionError("First child is cancelled") } val secondChild = launch { Log.d("TAG", "Second Child") } Log.d("TAG", "Cancelling supervisor") } Log.d("TAG", "End") } }
补充:
kotlin 协程异常处理
import kotlinx.coroutines.* import java.net.URL suspend fun fetchResponse(code: Int, delay: Int) = coroutineScope { val deferred: Deferred<String?> = async { URL("http://httpstat.us/$code?sleep=$delay").readText() } try { val response = deferred.await() println(response) } catch(ex: CancellationException) { println("${ex.message} for fetchResponse $code") } } runBlocking { val handler = CoroutineExceptionHandler {_, ex -> println("exception handled: ${ex.message}") } val job = launch(Dispatchers.IO + SupervisorJob() + handler) { // 协程1 launch{fetchResponse(202, 1000)} // 协程2 launch{fetchResponse(404, 2000)} // 协程3 launch{fetchResponse(200, 3000)} // 协程4 } job.join() }
如上的运行结果如下所示
202 Accepted
Parent job is Cancelling for fetchResponse 200
exception handled: http://httpstat.us/404?sleep=2000
分析:
一、fetchResponse使用的是coroutineScope来包起来,那么如果有协程的异常则该函数不会自己处理会向上传播;如果不想让协程的异常向外传播则可以使用supervisorScope
二、fetchResponse内部函数使用的是async/await, 并且try/catch没有将async包起来,那么如果async发生了异常会发生什么事情呢?
1、async作为 根协程(CoroutineScope或者 supervisorScope的直接子协程),异常不会自动抛出;它会在你调用await的时候才抛出所以这里的try/catch只是将await包起来即可
2、那么既然await的异常已经被捕获了,为何404还会向上抛出异常导致其他协程的取消呢?这是因为当async发生异常的时候会立即把异常抛出给父节点,因为父节点是coroutineScope所以继续向上抛出直到协程1,然后协程1将所有的子协程取消
那么问题来了:1中说的async不会抛出异常,2中又说会立即抛出异常;那么是否是自相矛盾呢?
async抛出的异常try/catch是没法捕获的,它会向父协程传播异常(即父子协程之间的异常传播,而不是代码级别的异常传播所以try/catch捕获不到;曾经使用catch(ex: Exception) 尝试捕获确实没有捕获成功)
综上所述,async跟await都是有抛出异常,只是他们抛出的方式不一样,async是父子协程之间的异常传播而await是代码逻辑的异常传播可被try/catch捕获到
那么问题来了:async不能被try/catch捕获到,该怎样捕获呢?
这里的launch使用了CoroutineExceptionHandler进行捕获;当异常传到协程1的时候,继续向上传播;因为协程1的父协程是SupervisorJob(为何协程1的父协程是SupervisorJob可以见https://www.jb51.net/program/2975787fp.htm)所以需要协程1自己处理;因为协程1的launch中包含了handler的异常捕获则会使用该异常类进行处理
这里launch可以使用CoroutineExceptionHandler进行捕获,那么async可以使用CoroutineExceptionHandler进行捕获么? 答案是不可以的,async对应的await需要使用try/catch进行捕获
注意:coroutineScope构建器中抛出的异常,或由协程创建的协程中抛出的异常,不会被 try/catch捕获!
具体的可参考:https://www.jb51.net/article/264218.htm
FQ版(英文版):https://medium.com/androiddevelopers/exceptions-in-coroutines-ce8da1ec060c
到此这篇关于Kotlin 协程 supervisorScope {} 运行崩溃解决的文章就介绍到这了,更多相关Kotlin 协程 supervisorScope {} 运行崩溃内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!