某個協(xié)程運行出現(xiàn)異常怎么辦旗扑?
當某個協(xié)程運行出現(xiàn)異常的時候,那么會有以下幾個操作:
- 取消自己的子級
- 取消自己
- 將異常傳播給父級
最新異常會到達CoroutineScope 的根本袍暴,并且CoroutineScope 啟動的所有協(xié)程都會被取消
當協(xié)程出現(xiàn)異常只取消自己,不影響其他協(xié)程
當協(xié)程運行出現(xiàn)異常的時候,我們希望不影響其他協(xié)程拄氯,只取消自己即可火诸,我們可以使用SupervisorJob 和supervisorScope
- 例子1使用普通的Job
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
val scopeParent = CoroutineScope(Job())
val scopeChild = CoroutineScope(Job())
val scopeSChild = CoroutineScope(SupervisorJob())
scopeParent.launch {
//child1
scopeChild.launch {
1 / 0
}
//child2
scopeChild.launch {
delay(1000)
println("你好")
}
}
Thread.sleep(2000)
}
Exception in thread "DefaultDispatcher-worker-2" java.lang.ArithmeticException: / by zero
at CoroutineKt$main$2$1.invokeSuspend(coroutine.kt:17)
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)
使用普通的Job的時候锦针,child1出現(xiàn)異常的時候child2也被取消,所以child2沒有執(zhí)行
- 例子2使用SupervisorJob
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
val scopeParent = CoroutineScope(Job())
val scopeChild = CoroutineScope(Job())
val scopeSChild = CoroutineScope(SupervisorJob())
scopeParent.launch {
//child1
scopeSChild.launch {
1 / 0
}
//child2
scopeSChild.launch {
delay(1000)
println("你好")
}
}
Thread.sleep(2000)
}
Exception in thread "DefaultDispatcher-worker-1" java.lang.ArithmeticException: / by zero
at CoroutineKt$main$2$1.invokeSuspend(coroutine.kt:11)
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)
你好
使用普通的SupervisorJob的時候置蜀,child1出現(xiàn)異常的時候child2繼續(xù)執(zhí)行
- 例子3使用supervisorScope
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
val scopeParent = CoroutineScope(Job())
val scopeChild = CoroutineScope(Job())
val scopeSChild = CoroutineScope(SupervisorJob())
scopeParent.launch {
//child1
supervisorScope {
launch {
1 / 0
}
}
//child2
supervisorScope {
launch {
delay(1000)
println("你好")
}
}
}
Thread.sleep(2000)
}
Exception in thread "DefaultDispatcher-worker-2" java.lang.ArithmeticException: / by zero
at CoroutineKt$main$2$1$1.invokeSuspend(coroutine.kt:12)
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)
你好
使用supervisorScope 也一樣奈搜,child1出現(xiàn)異常,不會影響到child2
- 例子3使用try catch
import kotlinx.coroutines.*
import java.lang.Exception
suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
val scopeParent = CoroutineScope(Job())
val scopeChild = CoroutineScope(Job())
val scopeSChild = CoroutineScope(SupervisorJob())
scopeParent.launch {
//child1
scopeChild.launch {
try {
1 / 0
} catch (e: Exception) {
println(e)
}
}
//child2
scopeChild.launch {
delay(1000)
println("你好")
}
}
Thread.sleep(2000)
}
java.lang.ArithmeticException: / by zero
你好
使用try catch把child1的異常捕捉之后盯荤,也不會影響到child2
處理異常
我們使用launch啟動的協(xié)程馋吗,我們使用try catch可以捕捉異常
scope.launch {
try {
codeThatCanThrowExceptions()
} catch(e: Exception) {
// 處理異常
}
}
如果我們使用了async,用于返回結(jié)果的協(xié)程秋秤,異常會在我們調(diào)用.await()方法的時候拋出
supervisorScope {
val deferred = async {
codeThatCanThrowExceptions()
}
try {
deferred.await()
} catch(e: Exception) {
// 處理 async 中拋出的異常
}
}
所以我們在使用async的時候耗美,不要將try catch放在async中,而是放在.await()中航缀。
CoroutineExceptionHandler
我們知道CoroutineExceptionHandler是CoroutineContext 上下文的一個可選的元素商架,當我們沒用使用try catch捕捉異常的時候,我們可以使用CoroutineExceptionHandler來捕捉異常芥玉。
val handler = CoroutineExceptionHandler {
context, exception -> println("Caught $exception")
}
使用CoroutineExceptionHandler捕捉異常的前提是通過launch啟動的協(xié)程
例如1:
import kotlinx.coroutines.*
import java.lang.Exception
suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
val scopeParent = CoroutineScope(Job())
val scopeChild = CoroutineScope(Job())
val scopeSChild = CoroutineScope(SupervisorJob())
val handler = CoroutineExceptionHandler { context, exception ->
println("Caught $exception")
}
scopeParent.launch(handler) {
1 / 0
//child2
}
Thread.sleep(2000)
}
Caught java.lang.ArithmeticException: / by zero
例如2:
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
val scopeParent = CoroutineScope(Job())
val scopeChild = CoroutineScope(Job())
val scopeSChild = CoroutineScope(SupervisorJob())
val handler = CoroutineExceptionHandler { context, exception ->
println("Caught $exception")
}
scopeParent.launch {
launch(handler) {
1 / 0
}
}
Thread.sleep(2000)
}
Exception in thread "DefaultDispatcher-worker-2" java.lang.ArithmeticException: / by zero
at CoroutineKt$main$2$1.invokeSuspend(coroutine.kt:15)
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)
以上協(xié)程的異常為什么沒用被捕捉呢蛇摸?因為我們在協(xié)程的內(nèi)部又啟動了一個子協(xié)程,當子協(xié)程報錯的時候灿巧,直接拋給了父級赶袄,父級不知道handler的存在,所以就沒用捕捉到抠藕。我們可以改成如下:
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的應用中某一層級的協(xié)程
val scopeParent = CoroutineScope(Job())
val scopeChild = CoroutineScope(Job())
val scopeSChild = CoroutineScope(SupervisorJob())
val handler = CoroutineExceptionHandler { context, exception ->
println("Caught $exception")
}
scopeParent.launch(handler) {
launch {
1 / 0
}
}
Thread.sleep(2000)
}
Caught java.lang.ArithmeticException: / by zero
我們讓父級接受handler 就能捕捉到了異常
小結(jié)
- 協(xié)程內(nèi)部出現(xiàn)異常的時候會直接取消自己和孩子并上報給父親饿肺,最后導致整個作用域取消
- 讓協(xié)程出現(xiàn)異常不影響其他協(xié)程,我們可以使用SupervisorJob和supervisorScope
- 處理協(xié)程的異常我們可以使用try catch盾似,當啟動協(xié)程的方法是aysnc的時候敬辣,協(xié)程的異常會在調(diào)用awaite的時候拋出,因此我們的try catch用在調(diào)用awaite
- 當我們沒有捕捉到異常的時候,這些異掣仍荆可以使用CoroutineExceptionHandler來捕捉村刨。它是上下文的一個可選參數(shù)