某个协程运行出现异常怎么办?
当某个协程运行出现异常的时候,那么会有以下几个操作:
- 取消自己的子级
- 取消自己
- 将异常传播给父级
最新异常会到达CoroutineScope 的根本,并且CoroutineScope 启动的所有协程都会被取消
当协程出现异常只取消自己,不影响其他协程
当协程运行出现异常的时候,我们希望不影响其他协程,只取消自己即可,我们可以使用SupervisorJob 和supervisorScope
- 例子1使用普通的Job
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的应用中某一层级的协程
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出现异常的时候child2也被取消,所以child2没有执行
- 例子2使用SupervisorJob
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的应用中某一层级的协程
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出现异常的时候child2继续执行
- 例子3使用supervisorScope
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的应用中某一层级的协程
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出现异常,不会影响到child2
- 例子3使用try catch
import kotlinx.coroutines.*
import java.lang.Exception
suspend fun main() {
// Scope 控制我的应用中某一层级的协程
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启动的协程,我们使用try catch可以捕捉异常
scope.launch {
try {
codeThatCanThrowExceptions()
} catch(e: Exception) {
// 处理异常
}
}
如果我们使用了async,用于返回结果的协程,异常会在我们调用.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启动的协程
例如1:
import kotlinx.coroutines.*
import java.lang.Exception
suspend fun main() {
// Scope 控制我的应用中某一层级的协程
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 控制我的应用中某一层级的协程
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)
以上协程的异常为什么没用被捕捉呢?因为我们在协程的内部又启动了一个子协程,当子协程报错的时候,直接抛给了父级,父级不知道handler的存在,所以就没用捕捉到。我们可以改成如下:
import kotlinx.coroutines.*
suspend fun main() {
// Scope 控制我的应用中某一层级的协程
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 就能捕捉到了异常
小结
- 协程内部出现异常的时候会直接取消自己和孩子并上报给父亲,最后导致整个作用域取消
- 让协程出现异常不影响其他协程,我们可以使用SupervisorJob和supervisorScope
- 处理协程的异常我们可以使用try catch,当启动协程的方法是aysnc的时候,协程的异常会在调用awaite的时候抛出,因此我们的try catch用在调用awaite
- 当我们没有捕捉到异常的时候,这些异常可以使用CoroutineExceptionHandler来捕捉。它是上下文的一个可选参数