美文网首页
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