Kotlin协程无法处理异常
Kotlin coroutine can't handle exception
我在玩协程,发现了一些非常奇怪的行为。我想使用 suspendCoroutine()
转换我项目中的一些异步请求。这是显示此问题的一段代码。
第一种情况,当在runBlocking
协程中调用挂起函数时,continuation的异常进入catch块,然后runBlocking
成功完成。但是在第二种情况下,当创建新的 async
协程时,异常会通过 catch 块并使整个程序崩溃。
package com.example.lib
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
object Test {
fun runSuccessfulCoroutine() {
runBlocking {
try {
Repository.fail()
} catch (ex: Throwable) {
println("Catching ex in runSuccessfulCoroutine(): $ex")
}
}
}
fun runFailingCoroutine() {
runBlocking {
try {
async { Repository.fail() }.await()
} catch (ex: Throwable) {
println("Catching ex in runFailingCoroutine(): $ex")
}
}
}
}
object Repository {
suspend fun fail(): Int = suspendCoroutine { cont ->
cont.resumeWithException(RuntimeException("Exception at ${Thread.currentThread().name}"))
}
}
fun main() {
Test.runSuccessfulCoroutine()
println()
Test.runFailingCoroutine()
println("We will never get here")
}
这是控制台上打印的内容:
Catching ex in runSuccessfulCoroutine(): java.lang.RuntimeException: Exception at main
Catching ex in runFailingCoroutine(): java.lang.RuntimeException: Exception at main
Exception in thread "main" java.lang.RuntimeException: Exception at main
at com.example.lib.Repository.fail(MyClass.kt:32)
at com.example.lib.Test$runFailingCoroutine.invokeSuspend(MyClass.kt:22)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
at kotlinx.coroutines.EventLoopBase.processNextEvent(EventLoop.kt:123)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:69)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:45)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.example.lib.Test.runFailingCoroutine(MyClass.kt:20)
at com.example.lib.MyClassKt.main(MyClass.kt:41)
at com.example.lib.MyClassKt.main(MyClass.kt)
Process finished with exit code 1
知道为什么会这样吗 - 是错误,还是我使用协程的方式不对?
更新:
使用 coroutineScope { ... }
将缓解 runFailingCoroutine()
中的问题
fun runFailingCoroutine() = runBlocking {
try {
coroutineScope { async { fail() }.await() }
} catch (ex: Throwable) {
println("Catching ex in runFailingCoroutine(): $ex")
}
}
你的第二个例子的行为是正确的,这是结构化并发的工作。
因为里面的async
块抛出异常,这个协程被取消了。由于结构化并发,父作业也被取消。
看看这个小例子:
val result = coroutineScope {
async {
throw IllegalStateException()
}
10
}
这个块永远不会 return 一个值,即使我们从不请求 async
结果。内层协程取消,外层作用域也取消。
如果您不喜欢这种行为,可以使用 supervisorScope
。在这种情况下,内部协程可以在外部协程失败的情况下失败。
val result = supervisorScope {
async {
throw IllegalStateException()
}
10
}
在您的第一个示例中,您在协程块内捕获了异常,因此,协程正常退出。
有关此主题的讨论,请参阅:
我昨天才被这种行为震惊,。
简而言之,这种行为是需要的,因为 async
与其他语言的目的不同。在 Kotlin 中,你应该谨慎使用它,只有当你必须将一个任务分解为多个 运行 并行的子任务时才使用。
只要你想写
val result = async { work() }.await()
你应该写
val result = withContext(Default) { work() }
这将按预期方式运行。此外,只要有机会,就应该将 withContext
调用移至 work()
函数中,并使其成为 suspend fun
.
我在玩协程,发现了一些非常奇怪的行为。我想使用 suspendCoroutine()
转换我项目中的一些异步请求。这是显示此问题的一段代码。
第一种情况,当在runBlocking
协程中调用挂起函数时,continuation的异常进入catch块,然后runBlocking
成功完成。但是在第二种情况下,当创建新的 async
协程时,异常会通过 catch 块并使整个程序崩溃。
package com.example.lib
import kotlinx.coroutines.async
import kotlinx.coroutines.runBlocking
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
object Test {
fun runSuccessfulCoroutine() {
runBlocking {
try {
Repository.fail()
} catch (ex: Throwable) {
println("Catching ex in runSuccessfulCoroutine(): $ex")
}
}
}
fun runFailingCoroutine() {
runBlocking {
try {
async { Repository.fail() }.await()
} catch (ex: Throwable) {
println("Catching ex in runFailingCoroutine(): $ex")
}
}
}
}
object Repository {
suspend fun fail(): Int = suspendCoroutine { cont ->
cont.resumeWithException(RuntimeException("Exception at ${Thread.currentThread().name}"))
}
}
fun main() {
Test.runSuccessfulCoroutine()
println()
Test.runFailingCoroutine()
println("We will never get here")
}
这是控制台上打印的内容:
Catching ex in runSuccessfulCoroutine(): java.lang.RuntimeException: Exception at main
Catching ex in runFailingCoroutine(): java.lang.RuntimeException: Exception at main
Exception in thread "main" java.lang.RuntimeException: Exception at main
at com.example.lib.Repository.fail(MyClass.kt:32)
at com.example.lib.Test$runFailingCoroutine.invokeSuspend(MyClass.kt:22)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:236)
at kotlinx.coroutines.EventLoopBase.processNextEvent(EventLoop.kt:123)
at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt:69)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt:45)
at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source)
at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking$default(Builders.kt:35)
at kotlinx.coroutines.BuildersKt.runBlocking$default(Unknown Source)
at com.example.lib.Test.runFailingCoroutine(MyClass.kt:20)
at com.example.lib.MyClassKt.main(MyClass.kt:41)
at com.example.lib.MyClassKt.main(MyClass.kt)
Process finished with exit code 1
知道为什么会这样吗 - 是错误,还是我使用协程的方式不对?
更新:
使用 coroutineScope { ... }
将缓解 runFailingCoroutine()
fun runFailingCoroutine() = runBlocking {
try {
coroutineScope { async { fail() }.await() }
} catch (ex: Throwable) {
println("Catching ex in runFailingCoroutine(): $ex")
}
}
你的第二个例子的行为是正确的,这是结构化并发的工作。
因为里面的async
块抛出异常,这个协程被取消了。由于结构化并发,父作业也被取消。
看看这个小例子:
val result = coroutineScope {
async {
throw IllegalStateException()
}
10
}
这个块永远不会 return 一个值,即使我们从不请求 async
结果。内层协程取消,外层作用域也取消。
如果您不喜欢这种行为,可以使用 supervisorScope
。在这种情况下,内部协程可以在外部协程失败的情况下失败。
val result = supervisorScope {
async {
throw IllegalStateException()
}
10
}
在您的第一个示例中,您在协程块内捕获了异常,因此,协程正常退出。
有关此主题的讨论,请参阅:
我昨天才被这种行为震惊,
简而言之,这种行为是需要的,因为 async
与其他语言的目的不同。在 Kotlin 中,你应该谨慎使用它,只有当你必须将一个任务分解为多个 运行 并行的子任务时才使用。
只要你想写
val result = async { work() }.await()
你应该写
val result = withContext(Default) { work() }
这将按预期方式运行。此外,只要有机会,就应该将 withContext
调用移至 work()
函数中,并使其成为 suspend fun
.