注:编码工具为IntelliJ
目录
Kotlin异常
不处理的情况
处理的情况
协程异常处理
launch方式启动的协程异常抛出和处理
异常抛出
常规处理:try-catch
常规处理二:try-catch包裹整个协程,不起作用
CoroutineExceptionHandler
async方式启动的协程异常抛出和处理
异常抛出
处理异常
CoroutineExceptionHandler
全局协程异常处理器
协同作用域与主从作用域异常处理的不同:针对协程嵌套
协同作用域
主从作用域
Kotlin异常
Kotlin中没有受检查异常,如果某段代码可能发生异常,需要手动try-catch。
不处理的情况
package step_fourteen
import java.io.File
fun main() {
File("").readLines()
}
输出:
Exception in thread "main" java.io.FileNotFoundException: at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at kotlin.io.FilesKt__FileReadWriteKt.forEachLine(FileReadWrite.kt:190) at kotlin.io.FilesKt__FileReadWriteKt.readLines(FileReadWrite.kt:219) at kotlin.io.FilesKt__FileReadWriteKt.readLines$default(FileReadWrite.kt:217) at step_fourteen.KotlinExceptionKt.main(KotlinException.kt:6) at step_fourteen.KotlinExceptionKt.main(KotlinException.kt)
处理的情况
package step_fourteen
import step_twelve.log
import java.io.File
import java.lang.Exception
fun main() {
try{
File("").readLines()
}catch (e: Exception)
{
log("捕获一个异常")
}
}
输出:
[2021-11-27 11:29:53]-[main] 捕获一个异常
协程异常处理
launch方式启动的协程异常抛出和处理
异常抛出
package step_fourteen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
val scope = CoroutineScope(Job())
scope.launch {
log("before")
File("").readLines()
log("after")
}
Thread.sleep(200)
}
异常抛出
package step_fourteen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
val scope = CoroutineScope(Job())
scope.launch {
log("before")
File("").readLines()
log("after")
}
Thread.sleep(200)
}
输出:
[2021-11-27 11:32:08]-[DefaultDispatcher-worker-1] before Exception in thread "DefaultDispatcher-worker-1" java.io.FileNotFoundException: at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at kotlin.io.FilesKt__FileReadWriteKt.forEachLine(FileReadWrite.kt:190) at kotlin.io.FilesKt__FileReadWriteKt.readLines(FileReadWrite.kt:219) at kotlin.io.FilesKt__FileReadWriteKt.readLines$default(FileReadWrite.kt:217) at step_fourteen.LaunchExceptionKt$main$1.invokeSuspend(LaunchException.kt:13) 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)
常规处理:try-catch
package step_fourteen
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
GlobalScope.launch {
log("before")
try{
File("").readLines()
}catch (e: Exception)
{
log("捕获异常")
}
log("after")
}
Thread.sleep(200)
}
输出:
[2021-11-27 11:34:33]-[DefaultDispatcher-worker-1] before [2021-11-27 11:34:33]-[DefaultDispatcher-worker-1] 捕获异常 [2021-11-27 11:34:33]-[DefaultDispatcher-worker-1] after
常规处理二:try-catch包裹整个协程,不起作用
package step_fourteen
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
try {
GlobalScope.launch {
log("before")
File("").readLines()
log("after")
}
} catch (e: Exception) {
log("捕获异常")
}
Thread.sleep(200)
}
输出:
[2021-11-27 11:35:02]-[DefaultDispatcher-worker-1] before Exception in thread "DefaultDispatcher-worker-1" java.io.FileNotFoundException: at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at kotlin.io.FilesKt__FileReadWriteKt.forEachLine(FileReadWrite.kt:190) at kotlin.io.FilesKt__FileReadWriteKt.readLines(FileReadWrite.kt:219) at kotlin.io.FilesKt__FileReadWriteKt.readLines$default(FileReadWrite.kt:217) at step_fourteen.LaunchExceptionKt$main$1.invokeSuspend(LaunchException.kt:12) 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)
CoroutineExceptionHandler
虽然捕获了异常,但是协程还是被取消了。
package step_fourteen
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
val handler = CoroutineExceptionHandler { _, e ->
log("handler 捕获异常 ")
}
GlobalScope.launch(handler) {
log("before")
File("").readLines()
log("after")
}
Thread.sleep(200)
}
输出:
[2021-11-27 11:36:59]-[DefaultDispatcher-worker-2] before [2021-11-27 11:36:59]-[DefaultDispatcher-worker-2] handler 捕获异常
handler对嵌套协程也适用,发生异常的协程会被取消,其他协程不受影响。
package step_fourteen
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
val handler = CoroutineExceptionHandler { _, e ->
log("handler 捕获异常 ")
}
GlobalScope.launch(handler) {
launch {
log("before")
File("").readLines()
log("after")
}
launch {
log("子协程")
}
}
Thread.sleep(200)
}
输出:
[2021-11-27 11:40:09]-[DefaultDispatcher-worker-3] 子协程 [2021-11-27 11:40:09]-[DefaultDispatcher-worker-1] before [2021-11-27 11:40:09]-[DefaultDispatcher-worker-1] handler 捕获异常
handler作为子协程的CoroutineContext,无效。
package step_fourteen
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
val handler = CoroutineExceptionHandler { _, e ->
log("handler 捕获异常 ")
}
GlobalScope.launch() {
launch(handler) {
log("before")
File("").readLines()
log("after")
}
launch {
log("子协程")
}
}
Thread.sleep(200)
}
输出:
[2021-11-27 11:43:22]-[DefaultDispatcher-worker-3] 子协程 [2021-11-27 11:43:22]-[DefaultDispatcher-worker-2] before Exception in thread "DefaultDispatcher-worker-2" java.io.FileNotFoundException: at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at kotlin.io.FilesKt__FileReadWriteKt.forEachLine(FileReadWrite.kt:190) at kotlin.io.FilesKt__FileReadWriteKt.readLines(FileReadWrite.kt:219) at kotlin.io.FilesKt__FileReadWriteKt.readLines$default(FileReadWrite.kt:217) at step_fourteen.LaunchExceptionKt$main$1$1.invokeSuspend(LaunchException.kt:16) 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)
小结:handler只在作为顶级作用域的协程上下文时有效。
async方式启动的协程异常抛出和处理 异常抛出
不抛出情况
package step_fourteen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import step_twelve.log
import java.io.File
fun main() {
val scope = CoroutineScope(Job())
scope.async{
log("before")
File("").readLines()
log("after")
}
Thread.sleep(200)
}
输出:
[2021-11-27 11:49:50]-[DefaultDispatcher-worker-1] before
抛出情况
package step_fourteen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import step_twelve.log
import java.io.File
fun main() {
runBlocking {
val scope = CoroutineScope(Job())
scope.async{
log("before")
File("").readLines()
log("after")
}.await()
Thread.sleep(200)
}
}
输出:
[2021-11-27 11:50:35]-[DefaultDispatcher-worker-1] before Exception in thread "main" java.io.FileNotFoundException: at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at kotlin.io.FilesKt__FileReadWriteKt.forEachLine(FileReadWrite.kt:190) at kotlin.io.FilesKt__FileReadWriteKt.readLines(FileReadWrite.kt:219) at kotlin.io.FilesKt__FileReadWriteKt.readLines$default(FileReadWrite.kt:217) at step_fourteen.AsyncExceptionKt$main$1$1.invokeSuspend(AsyncException.kt:15) 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)
处理异常
try-catch
package step_fourteen
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import step_twelve.log
import java.io.File
fun main() {
runBlocking {
val scope = CoroutineScope(Job())
val def = scope.async {
log("before")
File("").readLines()
log("after")
}
try {
def.await()
} catch (e: Exception) {
log("捕获异常")
}
Thread.sleep(200)
}
}
输出:
[2021-11-27 11:52:13]-[DefaultDispatcher-worker-1] before [2021-11-27 11:52:13]-[main] 捕获异常
CoroutineExceptionHandler
package step_fourteen
import kotlinx.coroutines.*
import step_twelve.log
import java.io.File
suspend fun main() {
val handler = CoroutineExceptionHandler { _, e ->
log("handler 捕获异常")
}
val scope = CoroutineScope(Job())
val def = scope.async(handler) {
log("before")
File("").readLines()
log("after")
}
def.await()
Thread.sleep(200)
}
输出:
[2021-11-27 11:54:03]-[DefaultDispatcher-worker-1] before Exception in thread "main" java.io.FileNotFoundException: at java.io.FileInputStream.open0(Native Method) at java.io.FileInputStream.open(FileInputStream.java:195) at java.io.FileInputStream.(FileInputStream.java:138) at kotlin.io.FilesKt__FileReadWriteKt.forEachLine(FileReadWrite.kt:190) at kotlin.io.FilesKt__FileReadWriteKt.readLines(FileReadWrite.kt:219) at kotlin.io.FilesKt__FileReadWriteKt.readLines$default(FileReadWrite.kt:217) at step_fourteen.AsyncExceptionKt$main$def$1.invokeSuspend(AsyncException.kt:15) 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)
小结:CoroutineExceptionHandler对launch启动方式有效,对async启动方式无效。
全局协程异常处理器
首先定义一个类实现CoroutineExceptionHandler
package step_fourteen
import kotlinx.coroutines.CoroutineExceptionHandler
import step_twelve.log
import kotlin.coroutines.CoroutineContext
class GlobalExceptionHandler: CoroutineExceptionHandler {
override val key: CoroutineContext.Key<*>
get() = CoroutineExceptionHandler
override fun handleException(context: CoroutineContext, exception: Throwable) {
Thread.setDefaultUncaughtExceptionHandler { t, e -> log("全局Handler捕获异常")}
}
}
src/main目录下创建resources目录,resources目录下创建meta-INF目录,meta-INF目录下创建services目录,services目录下创建名为kotlinx.coroutines.CoroutineExceptionHandler的文件,将自定义的Handler的全路径名写入其中,然后全局协程异常处理器就生效了。
全局异常处理器测试
package step_fourteen
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import step_twelve.log
import java.io.File
fun main() {
GlobalScope.launch {
log("before")
File("").readLines()
log("after")
}
Thread.sleep(200)
}
输出:
[2021-11-27 12:05:55]-[DefaultDispatcher-worker-2] before [2021-11-27 12:05:55]-[DefaultDispatcher-worker-2] 全局Handler捕获异常
全局异常处理器生效的原理:用到了JVM的ServiceLoader。
在源码的CoroutineExceptionHandlerImpl.kt文件中用ServiceLoader加载了我们自己定义的类,然后进行相应处理。
private val handlers: List= ServiceLoader.load( CoroutineExceptionHandler::class.java, CoroutineExceptionHandler::class.java.classLoader ).iterator().asSequence().toList() internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { // use additional extension handlers for (handler in handlers) { try { handler.handleException(context, exception) } catch (t: Throwable) { // Use thread's handler if custom handler failed to handle exception val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t)) } } // use thread's handler val currentThread = Thread.currentThread() currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) }
协同作用域与主从作用域异常处理的不同:针对协程嵌套
协同作用域
只要有一个子协程发生异常,所有协程都会被取消。
package step_fourteen
import kotlinx.coroutines.*
import step_twelve.log
fun main() {
GlobalScope.launch() {
launch() {
throw RuntimeException("abc")
}
launch {
log("test")
}
}
Thread.sleep(100)
}
输出:
Exception in thread "DefaultDispatcher-worker-1" java.lang.RuntimeException: abc at step_fourteen.ScopeTestKt$main$1$1.invokeSuspend(ScopeTest.kt:9) 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)
可以看到上面的子协程抛出异常,下面的子协程没有打印test。
主从作用域
有某个子协程发生异常,不影响其他协程运行。
package step_fourteen
import kotlinx.coroutines.*
import step_twelve.log
fun main() {
GlobalScope.launch() {
supervisorScope {
launch() {
throw RuntimeException("abc")
}
launch {
log("test")
}
}
}
Thread.sleep(100)
}
输出:
Exception in thread "DefaultDispatcher-worker-2" java.lang.RuntimeException: abc at step_fourteen.ScopeTest2Kt$main$1$1$1.invokeSuspend(ScopeTest2.kt:10) 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) [2021-11-27 12:14:14]-[DefaultDispatcher-worker-3] test
可以看到,虽然上面的协程抛出了异常,但下面的协程依然正常运行。
总结:
1、Job(launch启动方式返回值)的异常需要在根协程域捕获,类似 Thread.defaultUncaughtExceptionHandler,在子协程无法捕获,如果不捕获,会崩溃。
2、Defered(async启动方式返回值)的异常依赖用户来最终消费(await)异常,如果没消费,不需要捕获,也不会崩溃,如果消费了则需要捕获,不然会崩溃。
3、协同作用域中,协程中异常传播是双向的,子协程的异常会导致父协程结束,父协程的异常也会导致整个结束。
4、主从作用域中,SupervisorJob的异常是单向传播的,子协程的异常不影响其他,父协程的异常会影响全局,因此,SupervisorJob的子协程都应该设置捕获异常操作。
注意: 只有一级子协程和父协程之间才是主从关系: 单向传播; 而二级及以上子协程与父协程之间没有特殊声明,就是协同关系:双向传播。



