Why is join() required to allow the catch part of a try catch to execute for an uncaught exception in Kotlin coroutines?

huangapple go评论60阅读模式
英文:

Why is join() required to allow the catch part of a try catch to execute for an uncaught exception in Kotlin coroutines?

问题

为什么只有caughtExceptionTest1打印"caught Exception"?我理解协程将被取消,因为throwException()在launch {}范围内未被捕获,但为什么只有在我们join()抛出异常的Job时catch块才会执行?
英文:

I have this code

fun caughtExceptionTest1() {
  runBlocking {
    try {
      launch { throwException() }.join()
    } catch (e: Exception) {
      // This prints
      print("caught Exception")
    }
  }
}

fun caughtExceptionTest2() {
  runBlocking {
    try {
      launch { throwException() }
    } catch (e: Exception) {
      // This does not print
      print("caught Exception")
    }
  }
}

Why does only caughtExceptionTest1 print "caught Exception"? I understand that the coroutines will be cancelled because throwException() is uncaught in the launch {} scope but why does the catch block only execute if we join() the Job that throws the exception?

答案1

得分: 1

launch函数被设计成非阻塞的,意味着它启动一个协程但不等待其完成。因此,在caughtExceptionTest2中,try-catch在协程有机会抛出异常之前就已经完成,所以你的异常不会在那里被捕获。

与此同时,caughtExceptionTest1中的join会挂起当前的协程,直到它所调用的协程完成,这意味着在异常发生时try-catch块仍然处于活动状态,允许它捕获异常。

阅读更多:https://kotlinlang.org/docs/exception-handling.html

英文:

The launch function is designed to be non-blocking(!), meaning it starts a coroutine but doesn't wait for its completion. So in caughtExceptionTest2 the try-catch finishes before the coroutine has a chance to throw an exception, and your exception doesn't get caught there

Meanwhile the join in caughtExceptionTest1 suspends the current coroutine until the one it's invoked on completes, meaning the try-catch block is still active when the exception occurs, allowing it to catch the exception

Read more: https://kotlinlang.org/docs/exception-handling.html

答案2

得分: 1

只有翻译的部分:

请注意,您的第一个测试函数也没有捕获throwException()抛出的异常。它仍然会逃逸出该函数并被抛到caughtExceptionTest1()的调用方。您只捕获了由join()调用创建并抛出的CancellationException。

您两个函数之间唯一的区别是,在第一个函数中,通过调用join()生成了一个额外的异常,即CancellationException,然后立即捕获并记录它。

launch旨在将协程内部引发的未捕获异常传递给其父协程,因此它不会像您期望的那样将其抛给紧邻的代码。父协程然后处理它,行为取决于父协程的类型。在这种情况下,父协程是runBlocking协程,它将重新抛出异常给调用runBlocking的代码。

英文:

Note that your first test function is not catching the exception thrown by throwException() either. It will still escape the function and be thrown to the caller of caughtExceptionTest1(). You are only catching the CancellationException created by and thrown by your join() call.

The only difference between your two functions is that in the first one, you generate an extra Exception, the CancellationException, by calling join() and then immediately catch it and log it.

launch is designed to pass uncaught exceptions internal to the coroutine to its parent coroutine, so it doesn't throw it to the immediately surrounding code like you expected. The parent coroutine then handles it, and the behavior depends on what type of coroutine the parent is. In this case, the parent is the runBlocking coroutine, and it will rethrow the exception to the code that called runBlocking.

huangapple
  • 本文由 发表于 2023年6月9日 00:08:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/76433812.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定