Kotlin-coroutine yield()的作用是什么?

j2e*_*nue 6 coroutine kotlin

我不确定该yield功能的目的是什么。

你能检查我有这个例子吗?

无论如何,我在这里跟随一个例子。

这是代码:

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()
Run Code Online (Sandbox Code Playgroud)

当我注释掉第一个收益率时,我得到以下结果:

  • 取消孩子

    父母未取消

但是如果我保持原样,我会得到:

  • 取消孩子

    儿童被取消

    父母未取消

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

Xua*_*yen 21

我会在四个相关的事情的背景下回答这个问题:

  • 序列与协程yield(value: T)完全无关yield()
  • isActive只是一个标志,用于识别协程是否仍处于活动状态或已取消。您可以定期检查此标志并决定停止当前协程或继续。当然,通常情况下,我们只有在是的情况下才继续true。否则不要运行任何东西或抛出异常,例如。CancellationException
  • ensureActive()检查isActive上面的标志,CancellationException如果是 则抛出false
  • 协程yield()不仅首先调用ensureActive(),而且还礼貌地告诉同一个调度程序中的其他协程:“嘿,你们先走,然后我继续。” 原因可能是“目前我的工作没那么重要”。或者“很抱歉屏蔽你们这么久。我不是一个自私的人,所以现在轮到你们了。” 您可以完全按照字典中的含义来理解:“屈服(给某人/某物):允许较大道路上的车辆先行。” 同义词:让路。


Yur*_*mke 16

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

将当前协程调度程序的线程(或线程池)交给其他协程运行。如果协程调度器没有自己的线程池(如 Dispatchers.Unconfined),那么这个函数什么都不做,而是检查协程作业是否完成。该暂停功能是可以取消的。如果当前协程的 Job 在调用此挂起函数时或此函数等待调度时被取消或完成,则它会以 CancellationException 恢复。

它至少完成了几件事

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


Abh*_*kar 16

@Vasile 的答案与问题最相关,@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()
Run Code Online (Sandbox Code Playgroud)

输出:

Parent is running
Child is running
Cancelling child
Child is cancelled
Parent is not cancelled
Run Code Online (Sandbox Code Playgroud)

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


j2e*_*nue 9

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

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


Vas*_*ile 8

我对协程也很陌生,我对协程流程的理解是,将launch像上次执行一样执行,就在退出主函数之前。为了确定一些优先级,我们使用 - delay, yield, join。在这个例子中,我们可以改变yielddelay将会得到相同的结果。

流程:

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

  2. 应用程序跳过child= launchget 到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() 后,finally withprintln("Child is cancelled")将不会被执行,因为try之前没有触发块。

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


oiy*_*yio 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")
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

  • 它的工作原理完全相同,也没有屈服 (3认同)
  • 应该从代码中删除“delay”(因为它在延迟时有效地像“yield”一样工作) (2认同)