先說結論:
-
例外傳播機制的前提條件是建立一棵Job樹
-
當發生例外時,當前Job開始沿著樹向上逐級詢問父Job,它是否要處理
-
父Job B如果要處理,就沒子Job A什么事了,此時,Job B它自己就成了子Job B,繼續去問它的父Job C,一路問上去,直接找到一個愿意處理例外的Job,而所謂的愿意愿意處理的Job, 其實就是到達頂層Job, 它要是不處理, 那就沒人能處理了, 不論怎樣就它了.
-
父Job不愿意處理, 那就只好由子Job自己處理. 所謂的父Job不愿意處理, 有這幾種情況:
(1) 例外型別是CancellationException, 你取消就取消了唄, 多大點事, 你自己收尾
(2) 父Job是SupervisorJob, 他會重寫一個方法: childCancelled(ex) = false, 它告訴子Job, 不管是什么例外, 我都不管, 你自己處理.
-
不管例外是上報給父Job處理還是留著自己處理, 反正最后一定會有一個Job負責處理, 它會通過JobNode.CompletionHandler向下取消自己的所有子Job.
例外處理的入口
這個流程基本上是先看了一堆別人的博客, 然后自己邊看邊猜的
AbstractCoroutine.resumeWith(Result<T>) ->
JobSupport.makeCompletingOnce(result.toState()) ->
.tryMakeCompleting(...) ->
.tryMakeCompletingSlowPath() ->
.finalizeFinishingState() ->
// 最終的核心方法
(1) val handled = cancelParent(finalException) || handleJobException(finalException)
cancelParent 沿樹向上找到一個愿意處理此例外的Job物件
handleJobException 在沒有其他Job愿意處理時, 就只能自己處理
(2) completeStateFinalization() -> (state as CompletionHandler).invoke(cause)
把自己的所有子Job也都結束掉
cancelParent 沿樹向上找到一個愿意處理此例外的Job物件
先假設沒有SupervisorJob, 一路上全是Job, 而且也不是取消
private fun cancelParent(cause: Throwable): Boolean {
...
// 假設不是取消
val isCancellation = cause is CancellationException
val parent = parentHandle
// No parent -- ignore CE, report other exceptions.
if (parent === null || parent === NonDisposableHandle) {
return isCancellation = false
}
return parent.childCancelled(cause) || isCancellation = false
}
進入 parent.childCancelled(cause)
parent物件在構建Job樹時有提到, 在說到SupervisorJob時需要再強調一遍, 此處沿著Job樹往上找就可以了.
public open fun childCancelled(cause: Throwable): Boolean {
if (cause is CancellationException) return true // 假設不是取消
return cancelImpl(cause) && handlesException
}
此時進入parent物件中, 需要再次強調一遍, 面向物件的樹狀結構, 要時刻注意當前是在哪個物件上.
cancelImpl()中的代碼看不懂, 有一大堆的state, 只能憑感覺猜測 ->
makeCancelling(cause) ->
tryMakeCompleting(...) ->
tryMakeCompletingSlowPath() ->
finalizeFinishingState() ->
最終又回到了
cancelParent(finalException) || handleJobException(finalException)
然后就這樣一層一層得向上找, 直到盡頭:
if (parent === null || parent === NonDisposableHandle) {
return isCancellation 假設不是取消, return false
}
這樣就會進入 handleJobException(finalException), 由自己處理例外
上述程序中一直在強調, 假設它不是取消,
如果是取消, 要就有這幾種情形:
-
一路向上找到頂, return isCancellation = true,
-
中間任意一個父Job說它不處理, return parent.childCancelled(cause) = false || isCancellation = true, 最侄訓傳true
-
先提一句SupervisorJob, 它會重寫 childCancelled(ex) = false 表示自己不處理, return parent.childCancelled(cause) = false || isCancellation = true, 最侄訓傳true
然后它就不會進入 handleJobException(finalException)
SupervisorJob時需要再強調一遍的parent
scope.launch(SupervisorJob()) {
val newContext = 把scope.context的Job替換成引數SupervisorJob
val job = new StandaloneCoroutine(parentContext=newContext)
--> job.parentJob = parentContext[Job]=引數SupervisorJob
job.start(協程體)
return job
}
任務Job發生例外, 去詢問父Job是否要處理, 父Job是SupervisorJob, 它不處理.
這里潛藏的父子關系比較難找出來.
handleJobException(ex) 由自己去處理例外
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)
}
fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
// use thread's handler
val currentThread = Thread.currentThread()
currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
最侄訓呼叫到這里,哪個Job想處理這個例外,那就看看這個Job有沒有設定CoroutineExceptionHandler, 如果有, 那就由它來兜底處理.
如果沒有設定, 最侄訓執行 thread.uncaughtExceptionHandler.uncaughtException(), 而它沒辦法用try-catch捕獲,
coroutineScope{} 和 supervisorScope{}
這一條就憑感覺硬往前面的結論上湊的, 以后如果能非常清晰得搞清楚, 再來修改.
在看位元組小站的協程例外處理機制時,關于他的第7 第8點,按照前面分析代碼的結論,似乎走不通,直到我嘗試在第7條的try-catch里列印了錯誤堆疊:
2022-01-16 19:30:24.437 16178-16235/com.innocent.coroutine E/Innocent: caught : 測驗
java.lang.RuntimeException: 測驗
at com.innocent.coroutine.MainActivity$onCreate$1$1$1.invokeSuspend(MainActivity.kt:45)
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)
我恍然大悟, coroutineScope{} 不能把它理解為一個Scope物件, 而是應該理解為一個 subScope.launch(Job()) {}, 這樣再按照上述結論, 就可以跑通了.
subScope.launch(Job()) {
發生例外
}
subScope內部發生例外, 最終一路上報給subScope, 此時協程例外退出, 但因為它是sub, 跟launch很類似, 最侄訓是要通過 resumeWith()恢復主協程, 就在恢復主協程時, 內部例外就作為 resumeWith(Result.failure(ex)) 被捕獲.
subScope.launch(SupervisorJob()) {
發生例外
}
同樣的, supervisorScope{}也可以理解為 subScope.launch(SupervisorJob()) { }, 內部例外因為 SupervisorJob , 交給子Job處理, 如果此子Job有innerExceptionHandler, 那就可以由innerExceptionHandler處理. 假設它沒有, subScope會thread.uncaughtExceptionHandler.uncaughtException(), 因為它是sub, 所以由主協程最后兜底, 如有outterExceptionHandler則最終捕獲, 如無則繼續崩潰.
對這倆東西實在搞太不懂, 以后作業中我就盡量少用, 盡量用scope.launch{}方式
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/413936.html
標籤:其他
