【问题标题】:Why is Kotlin ignoring the delay(timeMillis: Long) function为什么 Kotlin 忽略了 delay(timeMillis: Long) 函数
【发布时间】:2020-01-07 10:41:08
【问题描述】:

上下文

遵循this point 的 Kotlin 协程基础指南

使用此代码

fun coroutinesAreLightWeight()
{
    runBlocking {
        repeat(100_000) {
            launch {
                delay(1000L)
                print("$it, ")
            }
        }
    }
}

问题

当我在我的计算机上运行该程序时,它会一次性打印出所有数字,而不是等待 1 秒后打印下一个数字。在运行 kotlin 指南中看到的确切代码时,此行为是相同的。似乎 delay() 函数被忽略了

起初这段代码运行良好,但随后停止按预期运行。我正在使用 IntelliJ 2019.2.1 和 kotlin 版本 1.3.50,我尝试重新启动程序,但这并没有解决我的问题。

这是整个班级的样子

class CoroutinesBasics
{
    fun ...

    fun ...

    fun coroutinesAreLightWeight()
    {
        runBlocking {
            repeat(100_000) {
                launch {
                    delay(1000L)
                    print("$it, ")
                }
            }
        }
    }
}

coroutinesAreLightWeight() 函数是这样调用的

fun main()
{
    CoroutineBasics().apply{
        ....
        ....
        coroutinesAreLightWeight()
    }
}

谁能指出发生了什么?这是 Kotlin 的错误吗?

Kotlin 依赖项

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'

【问题讨论】:

    标签: kotlin concurrency kotlin-coroutines


    【解决方案1】:

    据我所知,这是预期的行为,因为协同程序同时工作,因此您的代码只会等待所有当前工作的协同程序 1 秒。

    我推荐的关于协程的好演讲是: KotlinConf 2017 - Introduction to Coroutines by Roman Elizarov

    幻灯片中的示例完全相同:)

    如果您希望他们等待,您应该将重复移动到“runBlocking”之外

    【讨论】:

    • 但根据 Kotlin 协程指南,它显示“它启动 100K 协程,并且在一秒钟后,每个协程打印一个点。”
    • 你能在runBlocking { repeat(100_000) {之前打印一些东西吗
    • 或者只是增加延迟10秒,这样你就可以知道它是否在等待
    • 你的代码会发生什么:它将为你创建 100000 个协程,每个协程等待 1 秒然后打印。所以你将在一秒钟后打印 100000 次。因为该块内的所有代码都将同时工作
    • 因为delayThread.sleep 是完全不同的函数。 Thread.sleep是一个阻塞调用,会阻塞协程,每次调用sleep都会阻塞线程,除非线程被释放,否则其他协程将没有机会运行。 delay 不会阻止你的执行它会延迟协程本身
    【解决方案2】:

    我换个角度来回答。
    该示例是在特定上下文中给出的。即,“协程不像线程”。该示例的目的不是演示如何打印具有延迟的数字,而是演示协程与线程不同,可以同时启动数千个。这正是这段代码正在做的事情。它全部提交,然后全部执行。

    那么你可能会问,为什么它们都是连续的?他们不应该同时运行吗?
    为此,让我们打印线程名称:

    repeat(100_000) {
        launch {
           delay(100L)
           println("$it, ${Thread.currentThread().name}")
        }
    }
    

    你很快就会明白原因:99999, main

    由于您使用的是runBlocking,因此您的所有协程都由单个线程执行。

    不过,我们可以改变它:

    runBlocking {
        repeat(100_000) {
            launch(Dispatchers.Default) {
                delay(100L)
                println("$it, ${Thread.currentThread().name}")
            }
        }
    }
    

    通过使用Dispatchers.Default,我们在默认线程池上运行我们的协程。然后结果变得难以预测:

    98483, DefaultDispatcher-worker-6
    99898, DefaultDispatcher-worker-5
    99855, DefaultDispatcher-worker-1
    99706, DefaultDispatcher-worker-2
    

    默认线程池从 2 个线程开始,默认增加到 CPU 核心数。具体实现可以看createDefaultDispatcher()

    关于Thread.sleep(),你应该知道一些事情:

    1. 永远不要在协程中使用Thread.sleep()
    2. 永远不要在协程中使用Thread.sleep()
    3. IntelliJ 甚至会警告你不要在协程中使用 Thread.sleep()

    让我们看看下面的例子来理解为什么:

    repeat(100_000) {
       launch {
          Thread.sleep(100) 
          println("$it, ${Thread.currentThread().name}")
       }
    }
    

    您可能会假设您所说的是“在 100 毫秒内不要在此块中执行任何操作”。

    但您实际上是在说“在 100 毫秒内不要做任何事情”。

    由于launch 将在从runBlocking 获得的上下文中执行,而runBlocking 是一个单线程上下文,因此您会阻止所有协程的执行。

    【讨论】:

    • 我现在明白了,您的解释清晰简洁。我是否正确地说默认线程池中的线程是在需要时生成的?一个线程可以运行的任务数量有限制吗?
    • 当在launch 中使用Thread.sleep() 时,Kotlin 是否会一次性注册所有 100,000 个启动的协程,然后一个一个地执行它们?
    • 答案有些复杂,所以我会更新我最初的答案,而不是在cmets中回答你。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-12-23
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 2012-10-27
    相关资源
    最近更新 更多