【问题标题】:Kotlin coroutine can't handle exceptionKotlin协程无法处理异常
【发布时间】:2019-04-13 18:38:52
【问题描述】:

我在玩协程,发现了一些非常奇怪的行为。我想使用suspendCoroutine() 在我的项目中转换一些异步请求。这是显示此问题的一段代码。

在第一种情况下,当在 runBlocking 协程中调用挂起函数时,来自延续的异常进入 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$1$1.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")
    }
}

【问题讨论】:

    标签: exception kotlin kotlin-coroutines


    【解决方案1】:

    您的第二个示例的行为是正确的,这是结构化并发的工作。 因为内部的async 块抛出了异常,所以这个协程被取消了。由于结构化并发,父作业也被取消。

    看这个小例子:

    val result = coroutineScope {
        async {
            throw IllegalStateException()
        }
        10
    }
    

    即使我们从未请求async 结果,此块也永远不会返回值。内部协程被取消,外部作用域也被取消。

    如果您不喜欢这种行为,您可以使用supervisorScope。在这种情况下,内部协程可能会失败,而外部协程不会失败。

    val result = supervisorScope {
        async {
            throw IllegalStateException()
        }
        10
    }
    

    在您的第一个示例中,您在协程块内捕获异常,因此,协程正常退出。

    有关此主题的讨论,请参阅:

    【解决方案2】:

    就在昨天,我对这种行为感到震惊,here's my analysis

    简而言之,这种行为是合乎需要的,因为async 的用途与其他语言不同。在 Kotlin 中,您应该谨慎使用它,仅当您必须将一个任务分解为多个并行运行的子任务时。

    只要你只想写

    val result = async { work() }.await()
    

    你应该改为写

    val result = withContext(Default) { work() }
    

    这将按照预期的方式运行。此外,只要有机会,您应该将withContext 调用移到work() 函数中,并将其设为suspend fun

    【讨论】:

    • 谢谢,analysys 很有趣。另外,好一点,完全没有async { ... }.await()。但最初我发现了这种行为,当我需要将 3 个并行 api 调用压缩到一个对象中时,一个 api 调用中的异常导致整个应用程序崩溃。
    • 是的,您必须将您的三个异步调用放入 coroutineScope 构建器中,这样您就有一个单独的范围将被取消。您的主作用域应在其 override val coroutineContext 中使用 SupervisorJob() 而不仅仅是 Job()
    • @AlexanderSitnikov 可以使用GlobalScope.asyinc{ ... }.await(),为什么不应该完全使用asying { ... }.await()
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-11-13
    • 1970-01-01
    • 2018-04-12
    • 2019-10-17
    • 1970-01-01
    • 2018-05-26
    相关资源
    最近更新 更多