【问题标题】:kotlin coroutines: possible without standard library?kotlin 协程:可能没有标准库?
【发布时间】:2019-10-04 13:09:36
【问题描述】:

我的问题是理论上的。 我对 kotlin 很陌生(只通过了教程,没有写任何真正的代码)。

阅读语言参考后,我发现自己对“suspend”是一个关键字这一事实感到困惑,但我在关键字列表中找不到像“launch”这样的东西。这让我觉得有些不对称——“挂起”是一个编译器功能,而“启动”是一个库函数。我的理解正确吗?如果是这样 - 将两者都实现为库功能或同时实现为编译器功能不是更好吗?

我一直认为您可以随时使用可用的语言功能编写自己的标准库,但我仍然看不出这是否真的适用于这种情况。

TL;DR:我可以使用纯 kotlin 启动协程,而无需导入任何库(不管那会多么难看)?

【问题讨论】:

  • 您是否尝试过通过kotlinx.coroutines(我假设您出于某种原因想要替换它?)并查看它是如何完成的?
  • 是的,我做到了。如果你追踪它,那么你最终会得到类似 SafeContinuation::resumeWith() 的东西(我没有找到它的来源)。然而,这不是我的问题。我的问题是为什么“启动”是库函数而“挂起”是关键字?为什么会出现这种不对称?
  • 您可以在官方 kotlin 论坛或协程 github 上询问,以便有更好的机会引起实际做出该决定的人的注意。我的猜测是包含suspend 作为标准关键字只是开发人员团队的一个小“作弊”,否则他们需要求助于奇怪的标记注释。
  • 我似乎找到了一些可能相关的实现:coroutineCodegenUtil.kt 包含一个 invokeDoResumeWithUnit() 的实现,它包含几个堆栈交互和一个 invokevirtual()。我的猜测是这些被直接翻译成相应的字节码。如果 kotlin 给了我们直接生成字节码的能力,那么我更加困惑为什么“suspend”不是库函数而是关键字。但是谢谢你的指点,这似乎真的是要走的路。
  • 判断不过《Kotlin Coroutines Reloaded》中所说的,编译器支持不限于“suspend”关键字。似乎还有“启动协程”和“暂停协程”功能。 youtube.com/watch?time_continue=2440&v=3xalVUY69Ok

标签: kotlin coroutine kotlin-coroutines


【解决方案1】:

suspend 标记在函数签名中添加了一个隐藏的延续参数,并彻底改变了实现字节码。暂停点不归结为辅助函数调用,它们将您的线性程序代码变成状态机,状态保存在延续对象中。生成的字节码甚至无法表示为 Java 程序代码。

与此相反,launch 只是基于挂起/恢复原语的常规库代码。

【讨论】:

    【解决方案2】:

    我可以使用纯 kotlin 启动协程,而无需导入任何库(不管那会多么难看)?

    没有。所有协程生成器都在 kotlinx.coroutines 库中,因此您至少需要它。现在,从理论上讲,您可以自己重新实现此功能。但可能你不应该。

    对于 StackOverflow 的答案来说,如何做到这一点有点太长了,但是尝试从 Java 调用这个 Kotlin 类的方法:

    class AsyncWorks {
        suspend fun print() {
            println("Hello")
        }
    }
    

    您会看到,尽管 Kotlin 方法没有参数,但在 Java 中它需要 Continuation<? super Unit>。这就是 suspend 关键字的作用。它添加了Continuation<T> 作为我们函数的最后一个参数。

    将两者都实现为库功能不是更好吗? 都作为编译器功能?

    理想情况下,您希望所有内容都成为“库功能”,因为它更容易发展。从语言中删除关键字非常困难。理论上,可以避免使用suspend 作为关键字。 Quasar 是一个框架,而是使用注解。另一方面,Go 编程语言假设所有函数都是可暂停的。所有这些方法都有其优点和缺点。
    Kotlin 决定务实,并添加 suspend 关键字,将决定权留给开发人员。如果您对该主题感兴趣,我强烈推荐 Kotlin 协程的作者 Roman Elizarov 的演讲,该演讲解释了他们的决定:https://www.youtube.com/watch?v=Mj5P47F6nJg

    【讨论】:

    • 我不认为这是正确的。您可以在没有协程库的情况下使用协程并暂停代码。该库只是构建在编译器协程支持上的一堆很好的辅助函数和其他抽象(例如流)。请参阅下面的答案以获取示例。
    【解决方案3】:

    因为协程对don't support launch 的用例有效。因为suspend 需要一些specific support from the compilerlaunch 不需要如果你已经有suspend。因为结构化并发是语言功能之上的library framework,而launch 是该特定框架的一部分,它会根据语言要求做出特定选择。

    可以使用startCoroutine 启动没有任何库的协程。 kotlin.coroutines 是 Kotlin 的一部分,而不是库。

    【讨论】:

      【解决方案4】:

      在这里回答我自己的问题。

      在使用 Kotlin 一年后,我倾向于认为这确实是可能的。 suspend 语言功能会创建一个额外的类,并在每次调用挂起函数时实例化它。此类扩展 ContinuationImpl 并存储您的协程的进度 - 到目前为止它能够执行到该点。

      因此,需要编写一个自定义调度程序来管理延续对象的队列以决定现在必须运行哪个,以及一个launch 函数,它将接收新创建的延续对象并将其传递给给调度员。

      现在,这仍然是一个不对称 - ContinuationImpl 存在于 kotlin.coroutines.jvm.internal 中,因此编译器假定此包存在。如果真的想完全放弃标准库 - 他需要实现该包才能使用 suspend 关键字。

      虽然我不是 kotlin 专家,所以我可能错了。

      【讨论】:

        【解决方案5】:

        @Alexey Soshin 的说法不太正确。

        您可以在没有库的情况下使用协程,这非常简单。这是一个关于最简单的挂起协程示例,对协程库的依赖为 0。

        import kotlin.coroutines.*
        
        fun main() {
        
            lateinit var context: Continuation<Unit>
            
                suspend {
                    val extra="extra"
                    println("before suspend $extra")
                    suspendCoroutine<Unit> { context = it }
                    println("after suspend $extra")
                }.startCoroutine(
                    object : Continuation<Unit> {
                        override val context: CoroutineContext = EmptyCoroutineContext
                        // called when a coroutine ends. do nothing.
                        override fun resumeWith(result: Result<Unit>) {
                            result.onFailure { ex : Throwable -> throw ex }
                        }
                    }
                )
            
                println("kick it")
                context.resume(Unit)
        }
        

        这在 play.kotlinlang.org 网站上运行良好。

        从这段代码可以看出,任何用suspend 装饰的 lambda 上面都有startCourtine()

        实际上,我认为标准集合类中的SequenceBuilder() 使用像这样的简单协程来生成序列,而不依赖于协程库。

        编译器在协程上做繁重的工作,在每个可能的挂起点将代码分成不同的“方法”。看看这个的 java 代码,你会看到它被“拆分”成一个 switch 语句。暂停前一例,暂停后一例。

        图书馆为你做了很多好东西.....而且你可能几乎总是会使用它(因为为什么不呢?)但你实际上并不需要它。

        【讨论】:

        • 谢谢你,@West_JR。您的回答对我帮助很大,我能够更进一步!然而,问题是如果没有 standard 库是否可行。既然你有import,你还没有达到这个目标。
        • 我有点困惑。我不使用协程库。所有这些东西、流程、渠道和启动器等都在我没有导入的 kotlinx.* 中。我确实使用标准库......这基本上是 kotlin 运行时。我不知道任何可以在没有标准库的情况下运行的任何大小的 kotlin 应用程序。即使您没有显式导入它,“Int”和“String”都在标准库中,以及 main() 的启动方式。
        • 也许我的期望是错误的。我通常将编译器想象为基础层。在每种语言中都应该如此——无论是 Kotlin、Java、C 还是其他任何语言。标准库是用编译器可以理解的语言编写的。出于性能原因,某些部分可能会用更底层的语言编写,但这应该是可选的。在标准库上,其他一切都是构建的。因此,如果编译器知道标准库,那么这就是对抽象的破坏。
        猜你喜欢
        • 2021-04-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-10-02
        • 2019-04-05
        • 2020-03-13
        相关资源
        最近更新 更多