美文网首页
Kotlin 在 runBlocking 中 try catch

Kotlin 在 runBlocking 中 try catch

作者: 雁过留声_泪落无痕 | 来源:发表于2020-11-19 18:12 被阅读0次
    1. 先看如下代码,this 是 CoroutineScope 的实例
    private fun println(msg: String) {
        kotlin.io.println("msg: $msg, thread: ${Thread.currentThread().name}")
    }
    
    fun main() {
        runBlocking {
            println(this)
            val job = launch {
                println(this)
            }
            job.join()
        }
        println("end")
    }
    

    结果为

    BlockingCoroutine{Active}@2cdf8d8a
    StandaloneCoroutine{Active}@4f2410ac
    msg: end, thread: main
    

    可以知道前者为 BlockingCoroutine,后者为 StandaloneCoroutine

    private class BlockingCoroutine<T>(
        parentContext: CoroutineContext,
        private val blockedThread: Thread,
        private val eventLoop: EventLoop?
    ) : AbstractCoroutine<T>(parentContext, true) {
        ...
    }
    
    private open class StandaloneCoroutine(
        parentContext: CoroutineContext,
        active: Boolean
    ) : AbstractCoroutine<Unit>(parentContext, active) {
        override fun handleJobException(exception: Throwable): Boolean {
            handleCoroutineException(context, exception)
            return true
        }
    }
    

    发现二者都有 parentContext

    1. 我们用反射来打印下二者的 parentContext
    private fun getParentContext(scope: CoroutineScope): CoroutineContext {
        val kClass = scope.javaClass.kotlin
        val kCallable = kClass.members.last {
            it.name == "parentContext"
        }
        kCallable.isAccessible = true
        return kCallable.call(scope) as CoroutineContext
    }
    
    @ExperimentalStdlibApi
    fun main() {
        runBlocking {
            println(getParentContext(this))
            val job = launch {
                println(getParentContext(this))
            }
            job.join()
        }
        println("end")
    }
    

    结果为

    BlockingEventLoop@437da279
    [BlockingCoroutine{Active}@5ab956d7, BlockingEventLoop@437da279]
    msg: end, thread: main
    

    可以看出,后者的 parentContext 其实就是前者的 context

    1. 修改为 GlobalScope.launch
    fun main() {
        runBlocking {
            println(getParentContext(this))
            val job = GlobalScope.launch {
                println(getParentContext(this))
            }
            job.join()
        }
        println("end")
    }
    

    结果为

    BlockingEventLoop@36916eb0
    Dispatchers.Default
    msg: end, thread: main
    

    可以看出二者是没有父子关系的

    1. 现在我们抛出一个异常
    fun main() {
        runBlocking {
            try {
                val job = launch {
                    throw NullPointerException()
                }
                job.join()
            } catch (e: Throwable) {
                // do nothing.
            }
        }
        println("end")
    }
    

    结果为

    Exception in thread "main" java.lang.NullPointerException
        at com.xxx.test.TestCoroutineKt$main$1$job$1.invokeSuspend(TestCoroutine.kt:25)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
        at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt:274)
        at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:84)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:59)
        at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
        at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:38)
        at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
        at com.xiaobo.test.TestCoroutineKt.main(TestCoroutine.kt:22)
        at com.xiaobo.test.TestCoroutineKt.main(TestCoroutine.kt)
    
    Process finished with exit code 1
    

    没有打印 end,exit code 也不为 0,很明显进程是异常退出的,也就是发生了 crash

    1. 为什么 catch 不住异常呢?首先,这种情况下的异常是会传递到父协程的(详情看官网),所以会交给 BlockingCoroutine 来处理异常,来看 BlockingCoroutine 的代码
    private class BlockingCoroutine<T>(
        parentContext: CoroutineContext,
        private val blockedThread: Thread,
        private val eventLoop: EventLoop?
    ) : AbstractCoroutine<T>(parentContext, true) {
        override val isScopedCoroutine: Boolean get() = true
    
        override fun afterCompletion(state: Any?) {
            // wake up blocked thread
            if (Thread.currentThread() != blockedThread)
                LockSupport.unpark(blockedThread)
        }
    
        @Suppress("UNCHECKED_CAST")
        fun joinBlocking(): T {
            registerTimeLoopThread()
            try {
                eventLoop?.incrementUseCount()
                try {
                    while (true) {
                        @Suppress("DEPRECATION")
                        if (Thread.interrupted()) throw InterruptedException().also { cancelCoroutine(it) }
                        val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
                        // note: process next even may loose unpark flag, so check if completed before parking
                        if (isCompleted) break
                        parkNanos(this, parkNanos)
                    }
                } finally { // paranoia
                    eventLoop?.decrementUseCount()
                }
            } finally { // paranoia
                unregisterTimeLoopThread()
            }
            // now return result
            val state = this.state.unboxState()
            (state as? CompletedExceptionally)?.let { throw it.cause }
            return state as T
        }
    }
    

    异常会在这里进行再次抛出 (state as? CompletedExceptionally)?.let { throw it.cause },导致最终 crash

    1. 改为 GlobalScope.launch
    fun main() {
        runBlocking {
            try {
                val job = GlobalScope.launch {
                    throw NullPointerException()
                }
                job.join()
            } catch (e: Throwable) {
                // do nothing.
            }
        }
        println("end")
    }
    

    结果为

    msg: end, thread: main
    Exception in thread "DefaultDispatcher-worker-1" java.lang.NullPointerException
        at com.xxx.test.TestCoroutineKt$main$1$job$1.invokeSuspend(TestCoroutine.kt:25)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
    
    Process finished with exit code 0
    

    可以看到,打印了 end,而且 exit code 是 0,说明只是把异常信息打印了出来,并没有引发 crash

    1. 为什么这样不会 crash 呢?前面说了,这种情况下,GlobalScope.launch 自身就是根协程,其没有父协程,所以异常会由自己来处理,而自己是 StandaloneCoroutine,来看 StandaloneCoroutine 的代码
    private open class StandaloneCoroutine(
        parentContext: CoroutineContext,
        active: Boolean
    ) : AbstractCoroutine<Unit>(parentContext, active) {
        override fun handleJobException(exception: Throwable): Boolean {
            handleCoroutineException(context, exception)
            return true
        }
    }
    
    ==>
    
    public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
        // Invoke an exception handler from the context if present
        try {
            context[CoroutineExceptionHandler]?.let {
                it.handleException(context, exception)
                return
            }
        } catch (t: Throwable) {
            handleCoroutineExceptionImpl(context, handlerException(exception, t))
            return
        }
        // If a handler is not present in the context or an exception was thrown, fallback to the global handler
        handleCoroutineExceptionImpl(context, exception)
    }
    
    ==>
    
    internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
        // use additional extension handlers
        for (handler in handlers) {
            try {
                handler.handleException(context, exception)
            } catch (t: Throwable) {
                // Use thread's handler if custom handler failed to handle exception
                val currentThread = Thread.currentThread()
                currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
            }
        }
    
        // use thread's handler
        val currentThread = Thread.currentThread()
        currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
    }
    
    ==>
    
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        } else {
            Thread.UncaughtExceptionHandler ueh =
                Thread.getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            } else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \""
                                 + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
    

    最后会走到 ThreadGroup 的 uncaughtException 方法,调试发现 Thread.getDefaultUncaughtExceptionHandler() 返回为空(注意这里是在 Idea 中运行的纯 Kotlin 工程,Android 上使用了 Kotlin 的情况下返回的是 RuntimeInit$KillApplicationHandler,参见 Android 发生异常时的执行流程),所以仅仅是打印了异常发生的线程名,以及异常的堆栈信息。

    相关文章

      网友评论

          本文标题:Kotlin 在 runBlocking 中 try catch

          本文链接:https://www.haomeiwen.com/subject/hbsliktx.html