关键点
1.子协程未被捕获的异常会抛出到父协程,如果父协程没捕获异常,会导致父协程终止
2.launch/actor内发生的未被捕获的异常不会传递到父协程之外的地方
3.async/produce内发生的未被捕获的异常会传递到启动其父协程所在的线程
4.android原生的try catch无法捕获到不同线程抛出的异常
异常的传播
在协程中,不同的启动方式,对异常的传播处理不一样。
对于launch和actor构建器是不传播异常的,async和produce是传播异常的。 所谓的传播异常,是指能够将异常主动往外抛到启动顶层协程所在的线程; 传播异常表示不在本协程所在线程发生,异常直接往外抛到启动该协程所在的线程。
异常传播示例
不传播异常
globalScopeLaunch方式启动的是顶层协程,本身不存在父协程,在里面发生异常后, 只会再logCat输出异常异常,并不会影响到外部线程的运行。
fun globalScopeLaunch() {
println("start")
GlobalScope.launch {
println("launch Throwing exception")
throw NullPointerException()
}
Thread.sleep(3000)
//GlobalScope.launch产生的异常不影响该线程执行
println("end")
}
打印出:
start
launch Throwing exception
Exception in thread "DefaultDispatcher-worker-1 @coroutine#1" java.lang.NullPointerException.....
end
globalScopeLaunch方式启动的是顶层协程,本身不存在父协程,在里面发生异常后, 并不会影响到外部协程的运行,无论是否运行在同一个线程。
fun globalScopeLaunch() = runBlocking {
println("start ${Thread.currentThread().id}")
GlobalScope.launch {
println("launch Throwing exception ${Thread.currentThread().id}")
throw NullPointerException()
}.join()
GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
println("launch UNDISPATCHED Throwing exception ${Thread.currentThread().id}")
throw IndexOutOfBoundsException()
}.join()
//GlobalScope.launch产生的异常不影响该协程执行
println("end ${Thread.currentThread().id}")
}
打印出:
start 1
launch Throwing exception 11
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.NullPointerException... 异常打印
launch UNDISPATCHED Throwing exception 1
Exception in thread "main @coroutine#1" java.lang.IndexOutOfBoundsException... 异常打印
end 1
传播异常
async内发生的异常可以被抛出到启动该协程所在的线程上,但是需要显示使用await()方法才可以捕获到该异常
fun globalScopeAsync() = runBlocking {
println("start ${Thread.currentThread().id}")
val deferred = GlobalScope.async {
println("async Throwing exception ${Thread.currentThread().id}")
throw NullPointerException()
}
//deferred.join()
deferred.await()
//join不会接收异常可以正常执行下面步骤,await会接收到抛出的异常导致后面步骤无法执行
//不使用join和await则可以正常执行下面步骤
println("end ${Thread.currentThread().id}")
delay(3000)
}
使用join打印出:
start 1
async Throwing exception 11
end 1
使用await打印出:
start 1
async Throwing exception 12
java.lang.NullPointerException....抛出的异常
不使用join和await打印出:
start 1
end 1
async Throwing exception 11
也就意味着不在协程环境下启动async后,其内部抛出的异常无法被外部线程捕获,因为await需要在协程环境下才能调用
fun globalScopeAsync() {
println("start")
val deferred = GlobalScope.async {
println("async Throwing exception")
throw NullPointerException()
}
Thread.sleep(3000)
//非协程环境无法使用await,上边产生的异常不影响该线程执行
println("end")
}
打印出:
start
async Throwing exception
end
子协程未捕获的异常会导致父协程终止
子协程(无论是否与父协程在同一线程,async或launch启动)未被捕获的异常都会传播到父协程,导致父协程终止
fun launchChildException() = runBlocking {
println("start ${Thread.currentThread().id}")
//这边换成async也是一样结果
launch {
println("launch Throwing exception ${Thread.currentThread().id}")
throw NullPointerException()
}
delay(3000)
//因为子协程出现异常,默认会导致父协程终止,下边不会被执行
println("end ${Thread.currentThread().id}")
}
打印出:
start 1
launch Throwing exception 1
java.lang.NullPointerException....
fun launchChildExceptionIO() = runBlocking {
println("start ${Thread.currentThread().id}")
//这边换成async也是一样结果
launch(Dispatchers.IO) {
println("launch Throwing exception ${Thread.currentThread().id}")
throw NullPointerException()
}
delay(3000)
//因为子协程出现异常,默认会导致父协程终止,下边不会被执行
println("end ${Thread.currentThread().id}")
}
打印出:
start 1
launch Throwing exception 11
java.lang.NullPointerException....
异常捕获示例
使用try{}catch{}捕获
对deferred.await()进行try catch能正常捕获到错误, 但是对join try catch等同于没有。 这也意味着只有async的中发生的异常,才存在被try catch的可能。
fun asyncCatchException() = runBlocking {
println("start ${Thread.currentThread().id}")
val deferred = GlobalScope.async {
println("async Throwing exception ${Thread.currentThread().id}")
throw NullPointerException()
}
try {
//await能够被try catch捕获错误,从而继续玩下执行
deferred.await()
//join本身忽视错误,有没有try catch都一样
//deferred.join()
} catch (exception : NullPointerException) {
println("catch exception")
}
println("end ${Thread.currentThread().id}")
delay(3000)
}
await打印出:
start 1
async Throwing exception 11
catch exception
end 1
但是对join打印出:
start 1
async Throwing exception 12
end 1
CoroutineExceptionHandler捕获
对于launch这种不传播异常的协程,try catch无法捕获,可以使用CoroutineExceptionHandler, CoroutineExceptionHandler无法捕获async内的异常
fun coroutineExceptionHandler () = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
//可以捕获到launch中抛出的异常,但是不能捕获async
println("CoroutineExceptionHandler catch $throwable")
}
println("start ${Thread.currentThread().id}")
val job = GlobalScope.launch(handleException) {
println("launch Throwing exception ${Thread.currentThread().id}")
throw NullPointerException()
}
//job.join()
println("end ${Thread.currentThread().id}")
delay(3000)
}
打印出:
launch/launch.join打印出:
start 1
launch Throwing exception 11
CoroutineExceptionHandler catch java.lang.NullPointerException
end 1
CoroutineExceptionHandler无法捕获async内的异常,无论有没调用await()
fun asyncCoroutineExceptionHandler () = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
//可以捕获到launch中抛出的异常,但是不能捕获async
println("CoroutineExceptionHandler catch $throwable")
}
println("start ${Thread.currentThread().id}")
val referred = GlobalScope.async(handleException) {
println("async Throwing exception ${Thread.currentThread().id}")
throw IndexOutOfBoundsException()
}
// referred.join()
// referred.await()
println("end ${Thread.currentThread().id}")
delay(3000)
}
await打印出:
start 1
async Throwing exception 11
java.lang.IndexOutOfBoundsException...
没调用await打印:
start 1
async Throwing exception 11
end 1
特殊的CancellationException
处于delay的协程再被cancel()后会抛出CancellationException异常,该异常不被捕获也不会引起父协程的终止,也无法被CoroutineExceptionHandler捕获
fun launchCancelException() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
//无法捕获到launch中抛出的CancellationException异常
println("CoroutineExceptionHandler catch $throwable")
}
println("start ${Thread.currentThread().id}")
launch(handleException) {
println("launch Throwing exception ${Thread.currentThread().id}")
//不会导致父协程终止
throw CancellationException()
}
delay(3000)
//因为子协程出现异常,默认会导致父协程终止,但CancellationException不会
println("end ${Thread.currentThread().id}")
}
打印出:
start 1
launch Throwing exception 1
end 1
但是如果使用了await则异常会抛出在外部线程,需要进行捕获,否则会导致启动协程的线程崩溃
fun asyncCancelException() = runBlocking {
val handleException = CoroutineExceptionHandler { _, throwable ->
//无法捕获到launch中抛出的CancellationException异常
println("CoroutineExceptionHandler catch $throwable")
}
println("start ${Thread.currentThread().id}")
val deferred = async(handleException) {
println("async Throwing exception ${Thread.currentThread().id}")
//不会导致父协程终止
throw CancellationException()
}
//导致CancellationException异常抛出到该线程,下边无法执行
deferred.await()
//若没有await则下边可以正常执行
delay(3000)
println("end ${Thread.currentThread().id}")
}
打印出:
start 1
async Throwing exception 1
end 1
使用了await,打印出:
start 1
async Throwing exception 1
java.util.concurrent.CancellationException..