Reputation: 62519
I'm not exactly sure what is the purpose of the yield
function. Can you check this example I have?
I am following an example here.
Here is the code:
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()
When I comment out the first yield I get the following results:
Cancelling child
Parent is not cancelled
but if I leave the yield as it is I get:
Cancelling child
Child is cancelled
Parent is not cancelled
What does it mean to use yield
here?
Upvotes: 54
Views: 24047
Reputation: 247
With the information @j2emanue provided, it seems that without the first yield()
, the parent job by default would be marked the higher priority than the child job. Your code was not going to run into the child block as it should. It kept running to child.cancel()
and so on. That was why you get message "Parent is not cancelled"
. And you never got the message "Child is cancelled"
. In contrary you leave yield()
there, it would mark the child job to run with higher priority than the parent job. So you get the message "Child is cancelled"
before "Parent is not cancelled"
. Correct me if I give any wrong explaination.
Upvotes: 0
Reputation: 75
There is all right with @j2emanue's answer, but the word thread will be replaced with coroutine. if there are more corutines than threads that current dispatcher can offer and one of coroutines call yield then it's priority decrements and it let other coroutines to be run on that thread instead of the coroutine that called yield. this example shows the described above very well:
import kotlinx.coroutines.*
@OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class)
fun main() = runBlocking<Unit> {
val singleThreadDispatcher = newSingleThreadContext("JustThread")
runBlocking{
launch(singleThreadDispatcher){
while (true){
println("ab")
Thread.sleep(500)
yield()
}
}
launch(singleThreadDispatcher){
while (true){
println("cp")
Thread.sleep(500)
yield()
}
}
}
}
in this example coroutines work in turn. if remove the call of yield in both places then the first coroutine just occupies the thread and don't let the second one to run at all, if we remove only the second call to yield then the first coroutine after it's first call to yield never resumes again.
Upvotes: 0
Reputation: 24558
@Vasile's answer is the most relevant to the question, the accepted answer from @Yuri Schimke is just general information that doesn't actually answer the question.
To illustrate the need for the first yield
, let's change the code slightly by adding two "* is running" statements:
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()
Output:
Parent is running
Child is running
Cancelling child
Child is cancelled
Parent is not cancelled
Without the first yield
, "Child is running" is never printed since the child job doesn't get a chance to run. delay
suspends child execution and resumes parent execution. cancel
interrupts the delay
and moves the execution into the finally
block. The join
and the second yield
have no real effect, but by calling join
on the child job, we made absolutely sure that any following code is only executed once the child is completed/cancelled.
Upvotes: 21
Reputation: 736
I would answer the question in the context of 4 related things:
yield(value: T)
is totally unrelated to coroutine yield()
isActive
is just a flag to identify if the coroutine is still active or cancelled. You can check this flag periodically and decide to stop current coroutine or continue. Of course, normally, we only continue if it's true
. Otherwise don't run anything or throws exception, ex. CancellationException
.ensureActive()
checks the isActive
flag above and throws CancellationException
if it's false
.yield()
not only calls ensureActive()
first, but then also politely tells other coroutines in the same dispatcher that: "Hey, you guys could go first, then I will continue." The reason could be "My job is not so important at the moment." or "I am sorry to block you guys for so long. I am not a selfish person, so it's your turn now." You can understand here exactly like this meaning in dictionary: "yield (to somebody/something): to allow vehicles on a bigger road to go first." SYNONYM: give way.Upvotes: 29
Reputation: 1112
Read below comments in code for description:
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()
}
Upvotes: 2
Reputation: 62519
After some research, I see that the term yield
is actually from computer science and the term yielding a thread is what I did not understand.
essentially: yield()
basically means that the thread is not doing anything that important and if other threads need to be run, they can run. (I'd prefer to use join as Alex Yu mentioned). Basically, if we want to visualize what yield
is doing... whatever thread you call yield on will get pushed to the back of the messaging queue, then other threads with the same priority get executed ahead of it. So it's like going to the back of the line at a club.
Upvotes: 33
Reputation: 1369
suspend fun yield(): Unit (source)
Yields the thread (or thread pool) of the current coroutine
dispatcher to other coroutine
to run if possible.
This suspending function is cancellable. If the Job of the current coroutine
is canceled or completed when this suspending function is invoked or while this function is waiting for dispatch, it resumes with a CancellationException
.
Note: This function always checks for cancellation even when it does not suspend.
Upvotes: -2
Reputation: 5925
In your example, yield() is called inside parent job. It says to your parent: "you work much more, wait please, i will let the other tasks to work for some time, after some time i will let you continue to work ".
Thus parent job waits.. Child job works some time.. After some time, parent job passes the next line which comes after yield(), and cancels the child job.
If you do not use yield() in your example, parent job immediately cancels child job.
Let me explain the yield with different example which shows yield in much more clear way. 2 jobs wait in the queue for waiting thread to let them to work. When you call yield, thread looks into the queue and sees other job waiting so it lets the other job to work.
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")
}
Output:
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
Upvotes: 7
Reputation: 323
I'm also new to coroutines and my understanding of the coroutine flow is that the code inside the launch
will execute like the last execution, right before exiting main function. To put some priorities we use - delay, yield, join
. In this example we can change yield
with delay
and will be the same result.
The flow:
application jump over job = launch
get to job.join()
and
understand that the future code is waiting that job = launch' will
finish
application jump over child= launch
get to yield()
or
we can use delay(10)
and understand that future code is not
important and is going back to the beginning so to child= launch
get to delay(Long.MAX_VALUE)
it's a trap
application get to println("Cancelling child")
then to child.cancel()
and child.join()
who is a flow trigger. In this case we can substitute it with yield or join
. After this trigger application understand that child = launch
is canceled but finally
statement is not executed and execute it println("Child is cancelled")
.
Execute yield()
(i find it useless) then println("Parent is not cancelled")
.
Your Question --- yield() //why do i need this ???????
Because without yield
the app will not get back to child= launch
will not get inside try
block and after when the code will get to child.join(), finally
with println("Child is cancelled")
will not be executed because try
block was not triggered before.
I advice to run this code in debug mode and put breakpoints on every line and use "F9" in Intellij to understand the flow, and also experiment with Alex Yu code from playground and change delay, yield, join
Upvotes: 8
Reputation: 13458
https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
Yields a thread (or thread pool) of the current coroutine dispatcher to other coroutines to run. If the coroutine dispatcher does not have its own thread pool (like Dispatchers.Unconfined) then this function does nothing, but checks if the coroutine Job was completed. This suspending function is cancellable. If the Job of the current coroutine is cancelled or completed when this suspending function is invoked or while this function is waiting for dispatching, it resumes with CancellationException.
It accomplishes at least a few things
Upvotes: 31