程序在执行的时候,有些时候我们会遇到一些例外的情况,我们一般会使用 try-catch
来拦截程序执行所抛出的例外,用 try-catch 拦截到之後,我们就可以视情况要自己处理还是要再把这个例外转抛出去。(或是不处理,就让系统崩溃)
假设有一个函式,执行之後会有可能会抛出 RuntimeException
。所以当程序执行到这个函式的时候,就必须面临处理它的问题。
fun throwException() {
throw RuntimeException("Incorrect")
}
fun main(){
throwException()
}
→ 系统崩溃并收到一个错误讯息。(Exception in thread "main" java.lang.RuntimeException: Incorrect)
fun main(){
try {
throwException()
} catch (e: RuntimeException) {
println(e.message)
}
}
→ 在 try-catch
中拦截到这个例外,所以我们可以针对发生这个例外的情况来做处理。(如上方把错误资讯列印出来)
fun launchExceptionFun1(){
val job = launch {
launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("First children are cancelled")
}
}
launch {
delay(100)
println("Second child throws an exception")
throw RuntimeException()
}
}
job.join()
}
→ 这段程序使用 launch 建立了两个子 Coroutine ,一个 launch 执行一段很长时间的延迟,另一个则是延迟 100 毫秒之後,就抛出 RuntimeException()
。
执行这段程序码试试看:
流程如下,第二个 coroutine 抛出 RuntimeException
後,父 coroutine 的 Job 就把剩下的所有子 coroutine 取消。所以当例外发生的时候,後面还没有执行完成的 coroutine 就会被取消。
如果我们本来就知道哪一个函式会发生例外,我们可以直接使用 try-catch,把例外自行处理掉,就不会传到父 coroutine 来处理了。
将前面的范例改成:
launch {
delay(100)
println("Second child throws an exception")
try{
throw RuntimeException()
}catch(e: RuntimeException){
println("Catch exception")
}
}
→ 第一个 coroutine 不会因为第二个 coroutine 发生例外而被取消。
在建立 Coroutine 的时候,我们可以建立 CoroutineContext.Element 带入,其中有一个 Element 就是用来做例外处理的。
其名称为 CoroutineExceptionHandler
将上方程序改为:
class Day10 {
private val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
println("CoroutineExceptionHandler got $exception")
}
private val scope = CoroutineScope(coroutineExceptionHandler)
suspend fun launchWithException(){
val job = scope.launch {
launch {
try {
delay(Long.MAX_VALUE)
} finally {
println("First children are cancelled")
}
}
launch {
delay(100)
println("Second child throws an exception")
throw RuntimeException()
}
}
job.join()
}
}
→ 我们使用 CoroutineExceptionHandler
这个方法来建立 CoroutineExceptionHandler
的实例。
这边听起来很饶舌,在 Coroutine 中,有一个介面名为 CoroutineExceptionHandler ,它是继承 CoroutineContext.Element ,所以我们可以实作它并传进 Coroutine Context 中。
另外, Coroutine 也同时提供了一个函式,用来建立这个介面的实例,而这个函式的名称也叫做 CoroutineExceptionHandler。
这个方法实作如下:
public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineContext, Throwable) -> Unit): CoroutineExceptionHandler =
object : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler {
override fun handleException(context: CoroutineContext, exception: Throwable) =
handler.invoke(context, exception)
}
发生例外时就会调用 handleException,把 Coroutine context 以及 exception 传出去。
上面的程序改写完後,我们可以测试一下:
fun main() = runBlocking{
val day10 = Day10()
day10.launchWithException()
}
当我们的 Coroutine Scope 中有包含 CoroutineExceptionHandler 时,所有未经处理的例外都会传到这边,我们就可以在这个地方去做处理。
上面的范例是使用 launch
来示范的,如果是 async
,我们也可以使用 CoroutineExceptionHandler 来拦截例外吗?
suspend fun asyncException(): Int {
val deferred1 = scope.async {
delay(100)
10
}
val deferred2 = scope.async<Int> {
delay(200)
20
throw RuntimeException("Incorrect")
}
return deferred1.await() + deferred2.await()
}
→ 我们有一个函式,在这里面我们用 async
建立了两个 coroutine ,在第一个 Coroutine 中,我们延迟了 100 毫秒,并且回传整数10,而另外一个 coroutine ,我们延迟了 200 毫秒,但是在最後发生了 RuntimeException
。
→ 这个函式的结果需要将两个 async 的结果相加传出去。
好的,我们把这段程序码执行看看。
fun main() = runBlocking{
val day10 = Day10()
val result = day10.asyncException()
println($result)
}
在 Coroutine 中,只有 launch
以及 actor
里面的例外能够被 CoroutineExceptionHandler 给拦截, async
以及 produce
的例外则是会往外抛给使用者。
在这边我们可以使用 try-catch 来拦截,我们把上面的范例程序用 try-catch 包起来
fun main() = runBlocking{
val day10 = Day10()
try {
val result = day10.asyncException()
println("$result")
} catch (e: RuntimeException) {
println("${e.message}")
}
}
没错,用 try-catch 就可以把 async 的结果拦截下来了。
如果 Coroutine 的 job 为 Job(),在 Coroutine 一层一层的架构下,只要有一个 coroutine 发生例外就会导致其他的子 coroutine 被取消,如果想要避免这个情况,可以在可能发生例外的地方加上 try-catch 来作保护,让程序不会因为例外而取消所有的 coroutine。
假如我们没有把例外拦截下来,最後就会传到父 coroutine 的 CoroutineExceptionHandler (如果有设定的话)。
另外,launch 与 async 处理例外的方式各有不同, launch 是会往前传直到父 coroutine 的 CoroutineExceptionHandler,async 是把例外直接传给呼叫的地方,故我们需要在呼叫的地方使用 try-catch 拦截。
<<: 案例:MLOps在医疗产业(上) - 5个常见案例与3个风险来源
>>: [Day14] Esp32s用STA mode + LED - (程序码讲解)
猫咪万花筒 教学原文参考:猫咪万花筒 这篇文章会介绍,在 Scratch 3 里使用扩充功能的画笔,...
前言 这是一个七年 Android 工程师专为麻瓜写的。 麻瓜是指: 不会魔法、选错科系入错行、被老...
DAY03 建立 Datastore 和 Dataset (上) 我们都知道做 AI 最重要的就是 ...
前言 我们今天还是没有离开 user_bp,我们要来弄写文章的页面,也就是 markdown 上场的...
相信有人已经迫不及待要撰写文章了,不过在这之前,我们先来介绍一下 Markdown 这个标记语言。 ...