【问题标题】:What is the purpose of coroutine yield()?协程 yield() 的目的是什么?
【发布时间】:2019-07-24 20:06:46
【问题描述】:

我不确定yield 函数的用途是什么。你能检查一下我的这个例子吗?

我正在关注一个例子here

代码如下:

val job = launch {
    val child = launch {
        try {
            delay(Long.MAX_VALUE)
        } finally {
            println("Child is cancelled")
        }
    }
    yield() //why do i need this ???????
    println("Cancelling child")
    child.cancel()
    child.join()
    yield()
    println("Parent is not cancelled")
}
job.join()

当我注释掉第一个产量时,我得到以下结果:

  • 取消孩子

    父级未取消

但如果我保持收益不变,我会得到:

  • 取消孩子

    孩子被取消

    父级未取消

在这里使用yield是什么意思?

【问题讨论】:

  • 我自己现在不是 kotliner。在我看来更有趣的是:“我们需要child.join()吗?”。看看playground——我稍微修改了这个例子,并在几个地方使用了注释-取消注释yieldsjoins。看起来join 也产生了一个线程。
  • yield() 基本上意味着线程没有做任何重要的事情,如果需要运行其他线程,它们应该运行。所以是的,我想加入会这样做。我会调查的。谢谢

标签: kotlin coroutine


【解决方案1】:

阅读下面的代码中的cmets进行描述:

runBlocking {

        // Main job thread in example
        val job = launch {

            // Child job thread
            val child = launch {
                try {
                    println("logTag: Child before delay")
                    delay(Long.MAX_VALUE)
                    println("logTag: Child after delay")
                } finally {
                    println("logTag: Child is cancelled")
                }
            }

            // If you remove this yield, above child job thread will never execute.
            // And before execution, child job will be cancelled via below line child.cancel().
            // Here yield() is saying that thread (main job thread) is not doing anything that important and if other threads (i.e. child job thread) need to be run, they can run.
            // Try yourself, after commenting/uncommenting this yield(), and observe response.
            yield()

            println("logTag: Cancelling... child")
            child.cancel()
            child.join()

            // This is permitting other thread need to be run, if available.
            // This yield() doing nothing because there is no more active threads available. Because child job thread already cancelled above.
            // After commenting/uncommenting this, no change in response.
            yield()

            println("logTag: Parent is not cancelled")
        }
        job.join()
    }

【讨论】:

    【解决方案2】:

    经过一番研究,我发现yield 这个词实际上来自计算机科学,而产生一个线程的词是我不明白的。

    本质上:yield() 基本上意味着线程没有做任何重要的事情,如果需要运行其他线程,它们可以运行。 (我更喜欢使用 Alex Yu 提到的 join )。基本上,如果我们想可视化 yield 正在做什么......无论您调用什么线程,yield 都会被推到消息队列的后面,然后具有相同优先级的其他线程将在它之前执行。所以这就像在俱乐部里排在队伍的后面。

    【讨论】:

    • 有道理
    • 有趣的 yield() 不是来自 Java。它来自 kotlinx.coroutines。让步给其他线程的概念在许多语言中很常见,而不仅仅是 Java。
    • 谢谢你的权利,我更正了我的陈述。
    【解决方案3】:

    @Vasile 的answer 与问题最相关,@Yuri Schimke 接受的答案只是一般信息,实际上并没有回答问题。

    为了说明第一个yield 的必要性,让我们稍微修改一下代码,添加两个“* is running”语句:

    val job = launch {
        val child = launch {
            try {
                println("Child is running")
                delay(Long.MAX_VALUE)
            } finally {
                println("Child is cancelled")
            }
        }
        
        yield() // without this, child job doesn't get executed
        println("Cancelling child")
        child.cancel()
        child.join()
        yield()
        println("Parent is not cancelled")
    }
    println("Parent is running")
    job.join()
    

    输出:

    Parent is running
    Child is running
    Cancelling child
    Child is cancelled
    Parent is not cancelled
    

    如果没有第一个 yield,则永远不会打印“Child is running”,因为子作业没有机会运行。 delay 暂停子执行并恢复父执行。 cancel 中断delay 并将执行移至finally 块。 join 和第二个 yield 没有实际效果,但是通过在子作业上调用 join,我们绝对确保只有在子作业完成/取消后才会执行以下任何代码。

    【讨论】:

    • 这似乎有点苛刻。问题在于这段代码“我不确定 yield 函数的目的是什么。”使用规范的文档链接和“它暂时降低当前长时间运行的 CPU 任务的优先级,为其他任务提供公平的运行机会。”
    • @abhijit 这是一个很好的例子,谢谢。但接受的答案是重新陈述你已经正式写的内容。
    • @j2emanue "接受的答案是重新陈述你写的内容":我不这么认为,否则我不会花时间。但我们可以同意不同意,这不是竞争。
    【解决方案4】:
    suspend fun yield(): Unit (source)
    

    Yields 将当前coroutine 的线程(或线程池)调度到其他coroutine 尽可能运行。

    这个暂停功能是可以取消的。如果当前 coroutine 的 Job 在调用此挂起函数时或在此函数等待分派时被取消或完成,则以 CancellationException 恢复。

    注意: 这个函数总是检查取消,即使它没有挂起。

    【讨论】:

    • 协程yield与序列yield无关。
    【解决方案5】:

    在您的示例中,yield() 在父作业中被调用。它对你的父母说:“你工作多了,请稍等,我会让其他任务工作一段时间,过一段时间我会让你继续工作”。

    因此父作业等待.. 童工工作一段时间.. 一段时间后,父作业通过 yield() 之后的下一行,并取消子作业。

    如果您在示例中不使用 yield(),则父作业会立即取消子作业。

    让我用不同的例子来解释收益率,它以更清晰的方式显示收益率。 2 个作业在队列中等待等待线程让它们工作。当您调用 yield 时,线程会查看队列并看到其他作业正在等待,因此它让其他作业工作。

    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    import kotlinx.coroutines.runBlocking
    import kotlinx.coroutines.yield
    
    fun main() = runBlocking {
    
    
        val job1 = launch {
            repeat(10) {
                delay(1000)
                println("$it. step done in job 1 ")
                yield()
            }
        }
    
        val job2 = launch {
            repeat(10) {
                delay(1000)
                println("$it. step done in job 2 ")
                yield()
            }
        }
    
        job1.join()
        job2.join()
        println("done")
    }
    

    输出:

    0. step done in job 1 
    0. step done in job 2 
    1. step done in job 1 
    1. step done in job 2 
    2. step done in job 1 
    2. step done in job 2 
    3. step done in job 1 
    3. step done in job 2 
    4. step done in job 1 
    4. step done in job 2 
    5. step done in job 1 
    5. step done in job 2 
    6. step done in job 1 
    6. step done in job 2 
    7. step done in job 1 
    7. step done in job 2 
    8. step done in job 1 
    8. step done in job 2 
    9. step done in job 1 
    9. step done in job 2 
    done
    

    【讨论】:

    • 它的工作方式完全相同,但没有产量
    【解决方案6】:

    我也是协程的新手,我对协程流程的理解是launch 中的代码将像上次执行一样执行,就在退出主函数之前。我们使用一些优先级 - delay, yield, join。在此示例中,我们可以将 yield 更改为 delay,并且将得到相同的结果。

    流程:

    1. 应用程序跳过job = launch 到达job.join() 和 了解未来的代码正在等待 job = launch' 将 完成

    2. 应用程序跳过child= launch 到达yield() 或 我们可以使用delay(10) 并理解未来的代码不是 重要的是要回到开头所以child= launch

    3. delay(Long.MAX_VALUE)这是一个陷阱

    4. 应用程序到达println("Cancelling child"),然后到达child.cancel()child.join(),谁是流触发器。在这种情况下,我们可以将其替换为 yield or join 。在此触发器应用程序了解 child = launch 已取消但 finally 语句未执行并执行它之后 println("Child is cancelled")

    5. 执行yield()(我觉得没用)然后执行println("Parent is not cancelled")

      你的问题 --- yield() //为什么我需要这个 ??????? 因为没有yield,应用程序将无法返回child= launch 将无法进入try 块,当代码到达child.join() 之后,finallyprintln("Child is cancelled") 将不会被执行,因为@ 987654346@块之前没有被触发。

    我建议在调试模式下运行此代码并在每一行设置断点并在 Intellij 中使用“F9”来理解流程,并在 Playground 中试验 Alex Yu 代码并更改 delay, yield, join

    【讨论】:

      【解决方案7】:

      https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html

      产生当前协程调度器的一个线程(或线程池) 到其他协程运行。如果协程调度器没有 它自己的线程池(如 Dispatchers.Unconfined)然后这个函数 什么都不做,但检查协程 Job 是否完成。这 暂停功能是可以取消的。如果当前的工作 当这个挂起函数被取消或完成时协程 调用或在此函数等待调度时,它会恢复 带有 CancellationException。

      它至少完成了几件事

      1. 它会暂时降低当前长时间运行的 CPU 任务的优先级,为其他任务提供公平的运行机会。
      2. 检查当前作业是否被取消,否则在 CPU 密集型循环中,作业可能直到结束才检查。
      3. 允许子作业进行,因为作业多于线程而存在争用。如果当前作业应根据其他作业的进度进行调整,这可能很重要。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-27
        • 2020-11-17
        • 1970-01-01
        • 1970-01-01
        • 2017-09-16
        • 2020-06-15
        • 2017-10-28
        相关资源
        最近更新 更多