正如标题所说,为什么暂停函数会在finally
中抛出异常?
使用常规函数,finally
-block执行所有这些:
import kotlinx.coroutines.*
fun main() {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
launch {
// the first child
try {
println("inside try")
delay(1000)
} finally {
println("Children are cancelled, but exception is not handled until all children terminate")
Thread.sleep(1000)
println("thread.sleep executed")
//foo()
println("The first child finished its non cancellable block")
}
}
launch {
// the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
Thread.sleep(1000000)
println("complete")
}
在这里,例如,当我做Thread.sleep(1000)
它打印:
“第一个孩子完成了不可取消的区块”
但是,如果我将该行更改为delay(1000)
,则不会。
根据我的理解,在finally
-block中,异常(如果存在)在执行整个块之后抛出。
但在这种情况下,delay
会导致此异常提前抛出。
另一方面,Thread.sleep
没有。
有人可以解释一下吗?
Kotlin中的暂停功能与阻塞功能的工作方式不同。当您取消Job
时,在取消后的第一次暂停时,执行将被停止,即使您在finally
区块。如果你在你的Thread.sleep(1000)
区块中使用delay(1000)
而不是finally
,那么就没有停顿,因为Thread.sleep()
阻塞,而不是暂停,所以你的整个finally
块被执行。
请注意,在挂起函数中使用阻塞函数是一种反模式,应该避免!
要在不使用阻塞函数的情况下实现此期望的行为,请使用withContext(NonCancellable) {...}
描述的here。
您的示例代码应如下所示:
fun main() {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
launch {
// the first child
try {
println("inside try")
delay(1000000)
} finally {
withContext(NonCancellable) {
println("Children are cancelled, but exception is not handled until all children terminate")
delay(1000) // This suspension cannot be cancelled
println("delay executed")
//foo()
println("The first child finished its non cancellable block")
}
}
}
launch {
// the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
Thread.sleep(1000000)
println("complete")
}
输出:
inside try
Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
delay executed
The first child finished its non cancellable block
Caught java.lang.ArithmeticException
根据我的理解,在
finally
-block中,异常(如果存在)在执行整个块之后抛出。
这不是真的。如果finally
块抛出异常,则会导致finally
块突然终止该异常。因此丢弃在try
中抛出的任何异常。这正是你的情况下发生的事情:第一个儿童协程的finally
块在CancellationException
线上接收到delay(1000)
。 Thread.sleep(1000)
是一个阻塞,不可取消的功能,因此它没有观察到取消。
您可能将此与以下事实混淆:如果try
块抛出异常,则首先执行完整的finally
块,然后再抛出异常。 finally
块需要正常完成才能实现。
所以我相信你没有描述普通和可挂起函数的行为有任何区别。