【问题标题】:Why do suspending functions throw exceptions in finally为什么挂起函数在finally中抛出异常
【发布时间】:2019-03-03 05:25:33
【问题描述】:

正如标题所说,为什么挂起函数会在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


【解决方案1】:

Kotlin 中的挂起函数与阻塞函数的工作方式不同。 当您取消 Job 时,在取消后的第一次暂停时,执行将停止,即使如果您处于 finally 块中。如果您在finally 块中使用Thread.sleep(1000) 而不是delay(1000),则不会发生暂停,因为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

【讨论】:

    【解决方案2】:

    据我了解,在finally-block 中,如果存在异常,则会在执行整个块后抛出异常。

    这不是真的。如果finally 块抛出异常,它会导致finally 块以该异常突然终止。因此,try 中引发的任何异常都将被丢弃。这正是您的情况发生的情况:第一个子协程的 finally 块在 delay(1000) 行上收到 CancellationExceptionThread.sleep(1000) 是一个阻塞的、不可取消的函数,因此它不会观察到取消。

    您可能将这与以下事实混为一谈:如果try 块引发异常,则在引发异常之前首先执行完整的finally 块。 finally 块需要正常完成才能发生这种情况。

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

    【讨论】:

    • 啊哈!这会更有意义。听起来好像有某种隐含的isActive 或类似的函数可以确保当前协程及其父级都没有被取消(例如,它们没有被显式取消或遇到异常)
    • isActive 标志与Thread.isInterrupted() 标志直接平行。你必须在你的协程代码中显式地检查它,或者调用一个可挂起的函数来为你检查它。这样的功能可以通过声明为可取消来识别。
    • 所以挂起函数隐式调用它?
    • 没有什么是隐含的。就像您自己的函数必须显式检查它并通过抛出 CancellationException 做出反应一样,标准库函数也可以做到这一点。要么你通过调用suspendCancellableCoroutine来暂停协程。
    猜你喜欢
    • 1970-01-01
    • 2010-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-24
    • 2022-01-08
    相关资源
    最近更新 更多