- 先看如下代码,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
- 我们用反射来打印下二者的 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
- 修改为 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
可以看出二者是没有父子关系的
- 现在我们抛出一个异常
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
- 为什么 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
- 改为 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
- 为什么这样不会 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 发生异常时的执行流程),所以仅仅是打印了异常发生的线程名,以及异常的堆栈信息。
网友评论