为什么挂起函数最终会引发异常

时间:2019-03-03 05:25:33

标签: kotlin kotlinx.coroutines kotlin-coroutines

正如标题所述,为什么挂起函数会在finally中引发异常?

使用常规功能,finally块将执行所有这些功能:

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块中,如果存在异常,则在执行整个块后引发该异常。

但是在这种情况下,delay导致此异常尽早抛出。

另一方面,Thread.sleep没有。

有人可以帮忙解释吗?

2 个答案:

答案 0 :(得分:2)

Kotlin中的暂停功能与阻止功能的工作方式不同。 当您取消Job时,在取消后的第一个暂停处,执行将被停止,即使您处于finally块中,该操作也会甚至停止。如果您在Thread.sleep(1000)块中使用delay(1000)而不是finally,则不会发生暂停,因为Thread.sleep()阻止,而不是暂停,因此您的整个finally块都会被执行。

请注意,在挂起函数中使用阻塞函数是一种反模式,应避免!

要在不使用阻止功能的情况下实现此期望的行为,请按照here所述使用withContext(NonCancellable) {...}

您的示例代码应如下所示:

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

答案 1 :(得分:0)

  

据我了解,在finally块中,如果存在异常,则在执行整个块后引发该异常。

这不是事实。如果finally块引发异常,则会导致finally块因该异常而突然终止。因此,在try中引发的任何异常都将被丢弃。这正是您所遇到的情况:第一个子协程的finally块在CancellationException行上收到了delay(1000)Thread.sleep(1000)是一项不可取消的阻止性功能,因此它不会观察到取消。

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

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