为什么挂起函数最终抛出异常

问题描述 投票:0回答:2

正如标题所说,为什么暂停函数会在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 kotlinx.coroutines kotlin-coroutines
2个回答
2
投票

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

0
投票

根据我的理解,在finally-block中,异常(如果存在)在执行整个块之后抛出。

这不是真的。如果finally块抛出异常,则会导致finally块突然终止该异常。因此丢弃在try中抛出的任何异常。这正是你的情况下发生的事情:第一个儿童协程的finally块在CancellationException线上接收到delay(1000)Thread.sleep(1000)是一个阻塞,不可取消的功能,因此它没有观察到取消。

您可能将此与以下事实混淆:如果try块抛出异常,则首先执行完整的finally块,然后再抛出异常。 finally块需要正常完成才能实现。

所以我相信你没有描述普通和可挂起函数的行为有任何区别。

© www.soinside.com 2019 - 2024. All rights reserved.