Coroutine支援kotlin一般的Exception處理 try/catch/finally, 或是runningCatch (內部依然使用try/catch), 為了避免有人是直接看這篇,在講一下coroutine怎麼傳播Exception的
某個child coroutine有excpetion,通知parent,parent取消他的sibling,往上一層通知parent,直到root scope
以一個普通的coroutine來說,只要有throw Exception,從root開始的coroutine結果論都會被取消
儘管這個設計適用某些情境,但也有不適合的時候,比如有個ui relative coroutine throw Exception,那整個ui會無法響應,因為已取消的coroutine無發在開啟新的coroutine
大家應該都還記得之前講過的supervisor部分吧,他可以向coroutine表示,我會處理這個Exception,所以你不必把其他的coroutine取消,在白話一點就是
[child] 我有Exception喔
[parent] ok
而它的開啟方式有兩種
val scope = CoroutineScope(SupervisorJob())
scope.launch {
}
//或是
val scope = CoroutineScope(Job())
scope.launch {
supervisorScope {
launch {
// Child 1
}
launch {
// Child 2
}
}
}
supervisorJob或是supervisorScope只有在創建scope時傳入才有效
Remember that a SupervisorJob only works as described when it’s part of a scope: either created using supervisorScope or CoroutineScope(SupervisorJob()).
supervisorJob的scope之下建立的子coroutine,即使丟出Exception也不會影響其sibiling,在錯誤處理上和coroutoineScope不同,supervisorScope並不會因為其中一個網路請求回報錯誤而取消該作用域,也就代表著它可以拿到其他正常回報的網路請求; 但同等重要的,如果這個Exception沒有被處理,或是沒有CoroutineExceptionHandler,他會執行default thread的ExceptionHandler,在android就會爆掉~
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: com.kenny.androidplayground, PID: 23611
java.lang.Exception: I throw a Exception
? Uncaught exceptions will always be thrown regardless of the kind of Job you use
在launch, exception會再發生地當下立刻被丟出
圖源兼資料來源
catch them all
錯誤用法:詳情參考繼承篇
try catch的誤用
儘管try/catch看似很直觀,但在try裡面的Exception會被catch,對吧?
CoroutineScope(SupervisorJob()).launch {
try {
launch {
throw Exception("I throw an Exception")
}
} catch (e:Exception){
Timber.e(e)
}
launch {
Timber.d("zero")
}
}
爆惹
E/CoroutineFragment$test: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine is cancelling; job=StandaloneCoroutine{Cancelling}@3ee750d
Caused by: java.lang.Exception: I throw an Exception
再改,又爆惹
val scope = CoroutineScope(SupervisorJob())
scope.launch {
// Child 1
throw Exception("I throw an Exception")
}
scope.launch {
Timber.d("ha")
// Child 2
}
等等,官方的博文明明是這麼說的
// Scope handling coroutines for a particular layer of my app
val scope = CoroutineScope(SupervisorJob())
scope.launch {
// Child 1
}
scope.launch {
// Child 2
}
//In this case, if child#1 fails, neither scope nor child#2 will be cancelled.
不怕不怕,我們接著讀
正如我之前提過的,coroutine有一套自己的Exception傳播系統,但try/ catch也並非毫無用處,launch和async的Exception處理方式不同,所以我們要用不同的方式去catch
launch會再發生地當下立刻丟出,正確的作法是
val scope = CoroutineScope(SupervisorJob())
scope.launch {
try {
//somethingDanger()
throw Exception("I throw an Exception")
} catch (e:Exception){
Timber.e(e)
}
}
scope.launch {
Timber.d("ha")
// Child 2
}
E/CoroutineFragment$test: java.lang.Exception: I throw an Exception
at com.kenny.androidplayground.coroutineUi.CoroutineFragment$test$1.invokeSuspend(CoroutineFragment.kt:39)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
D/CoroutineFragment$test: ha
try/catch包住可能會出錯的Code,而不是包住外面的launch
async就要考慮狀況,當他是root coroutine (coroutines that are a direct child of a CoroutineScope instance or supervisorScope), Exception不會自動發出,而是會等你呼叫await()
a SupervisorJob lets the coroutine handle the exception
注意一下,我們是在supervisorScope 裡面呼叫async and await的,因為Exception會到await呼叫時才會丟出,所以他不用包再try/ catch裡面
supervisorScope {
val deferred = async {
codeThatCanThrowExceptions()
}
try {
deferred.await()
} catch(e: Exception) {
// Handle exception thrown in async
}
}
如果是用Job,即使用try/catch也會爆,原因和launch一樣,coroutine有自己傳遞Exception的規則
coroutineScope {
try {
val deferred = async {
codeThatCanThrowExceptions()
}
deferred.await()
} catch(e: Exception) {
// Exception thrown in async WILL NOT be caught here
// but propagated up to the scope
}
}
如果這樣寫,不用呼叫await,Exception就會往上傳
The reason is that async (with a Job in its CoroutineContext) will automatically propagate the exception up to its parent (launch) that will throw the exception.
scope.launch {
async {
// If async throws, launch throws without calling .await()
}
}
Warning: A SupervisorJob only works as described when it’s part of a scope: either created using supervisorScope or CoroutineScope(SupervisorJob()).
https://cloud.tencent.com/developer/article/1605877
那除了try/catch我們還有其他的處理方法,那就是在CoroutineContext講過的CoroutineExceptionHandler
whenever an exception is caught, you have information about the CoroutineContext where the exception happened and the exception itself
用法大概這樣
val mHandler = CoroutineExceptionHandler {
context, exception -> println("Caught $exception")
}
CoroutineExceptionHandler有幾個特點
When ⏰: The exception is thrown by a coroutine that automatically throws exceptions (works with launch, not with async).
Where ?: If it’s in the CoroutineContext of a CoroutineScope or a root coroutine (direct child of CoroutineScope or a supervisorScope).
看不懂?給你一個例子
val scope = CoroutineScope(Job())
scope.launch(mHandler) {
launch {
throw Exception("Failed coroutine")
}
}
再給一個錯誤例子
val scope = CoroutineScope(Job())
scope.launch {
launch(mHandler) {
throw Exception("Failed coroutine")
}
}
The exception isn’t caught because the handler is not installed in the right CoroutineContext. The inner launch will propagate the exception up to the parent as soon as it happens, since the parent doesn’t know anything about the handler, the exception will be thrown.
基本使用
lifecycleScope.launch (mHandler){
throw Exception("I throw an Exception")
}
//Caught java.lang.Exception: I throw an Exception
那如果我們同時用Handler和try/catch呢?
lifecycleScope.launch (mHandler){
try {
throw Exception("I throw an Exception")
} catch(e: Exception) {
Timber.e("try/catch got $e")
}
}
//try/catch got java.lang.Exception: I throw an Exception
合理
必看官方博文
https://www.kotlincn.net/docs/reference/coroutines/exception-handling.html