【问题标题】:Kotlin coroutines `runBlocking`Kotlin 协程`runBlocking`
【发布时间】:2019-02-19 05:33:30
【问题描述】:

我正在学习 Kotlin 协程。我读过runBlocking 是桥接同步和异步代码的方法。但是如果runBlocking 停止 UI 线程,性能提升是多少? 比如我需要在Android中查询一个数据库:

    val result: Int
    get() = runBlocking { queryDatabase().await() }

private fun queryDatabase(): Deferred<Int> {
    return async {
        var cursor: Cursor? = null
        var queryResult: Int = 0
        val sqlQuery = "SELECT COUNT(ID) FROM TABLE..."
        try {
            cursor = getHelper().readableDatabase.query(sqlQuery)
            cursor?.moveToFirst()
            queryResult = cursor?.getInt(0) ?: 0
        } catch (e: Exception) {
            Log.e(TAG, e.localizedMessage)
        } finally {
            cursor?.close()
        }
        return@async queryResult
    }
}

查询数据库会停止主线程,所以看起来它需要与同步代码相同的时间?如果我遗漏了什么,请纠正我。

【问题讨论】:

    标签: android kotlin kotlin-coroutines


    【解决方案1】:

    实际上,您使用runBlocking 在“阻塞”代码中调用挂起函数,否则将无法在其中调用,换句话说:您使用它在协程上下文之外调用suspend 函数(在您的示例中传递给async 的块是suspend 函数)。此外(更明显,正如其名称本身已经暗示的那样),该调用是阻塞调用。因此,在您的示例中,它的执行就像没有 async 之类的东西一样。它会等待(阻塞中断)直到runBlocking-block 中的所有内容都完成。

    例如,假设您的库中的函数如下:

    suspend fun demo() : Any = TODO()
    

    这个方法不能被调用,例如main。对于这种情况,您可以使用runBlocking,例如:

    fun main(args: Array<String>) {
      // demo() // this alone wouldn't compile... Error:() Kotlin: Suspend function 'demo' should be called only from a coroutine or another suspend function
      // whereas the following works as intended:
      runBlocking {
        demo()
      } // it also waits until demo()-call is finished which wouldn't happen if you use launch
    }
    

    关于性能提升:实际上,您的应用程序可能更具有响应性,而不是性能更高(有时性能也更高,例如,如果您有多个并行操作而不是多个连续操作)。但是,在您的示例中,您在分配变量时已经阻塞,所以我想说您的应用程序还没有得到更多响应。您可能更希望异步调用您的查询,然后在响应可用时立即更新 UI。所以你基本上只是省略runBlocking,而是使用launch之类的东西。你可能也对Guide to UI programming with coroutines感兴趣。

    【讨论】:

    • 您能否解释一下“在协程上下文之外调用挂起函数”是什么意思?意思是?您的意思是挂起在runBlocking 上下文之外?我同意,但 runBlocking 只会在挂起函数返回后返回,因此我的选项中没有速度增益。唯一的区别是查询是在不同的线程上执行的
    • 添加了我的意思的示例...如果不清楚,请询问
    • 另请注意,对于您真正想要异步运行的情况,您不要使用runBlocking...
    • 稍微更新了我的答案...关于性能提升...请注意,您当前的代码实际上与没有协程的同步变体几乎相同...只需尝试省略runBlocking并使用launch,您基本上会体验到异步调用;-)
    • 感谢这比文档更容易理解
    【解决方案2】:

    runBlocking是桥接同步和异步代码的方式

    我经常碰到这个短语,它非常具有误导性。

    runBlocking几乎从不是您在生产中使用的工具。它取消了协程的异步、非阻塞特性。如果您碰巧已经有一些基于协程的代码想要在协程不提供任何价值的上下文中使用:在阻塞调用中,您可以使用它。一种典型的用途是 JUnit 测试,其中测试方法必须坐下来等待协程完成。

    您也可以在main 方法中使用它来玩协程。

    runBlocking 的滥用已经变得如此普遍,以至于 Kotlin 团队实际上试图添加一个快速失败检查,如果你在 UI 线程上调用它会立即使你的代码崩溃。到他们这样做的时候,它已经破坏了很多代码,以至于他们不得不将其删除。

    【讨论】:

    • runBlocking 的滥用已经变得如此普遍,以至于 Kotlin 团队实际上不得不通过 fail-fast 恢复更改 :)
    • 这种对revert fail-fast的解释实际上是误导性的,这不仅是误用的问题(肯定存在),问题在于存在有效的用例,例如应用程序启动,资源清理等,您可以在协程问题跟踪器上看到有关它的讨论,带有附加参数的解决方案“是的,阻止,我知道我在做什么,请不要崩溃”不会更好 imo。另一个问题是它在发布代码上失败了,但在调试时却失败了,这是错误的策略导致错误,而不是阻止它们
    • 应该用什么代替runBlocking来调用协程而不阻塞?
    • @Trevor 你应该 launch 一个协程。
    • @user3410835 runBlocking 返回它启动的协程的结果,这意味着它必须等待它完成。
    猜你喜欢
    • 1970-01-01
    • 2019-07-17
    • 1970-01-01
    • 2023-03-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-04-19
    • 1970-01-01
    相关资源
    最近更新 更多