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?
Yields the thread (or thread pool) of the current coroutine dispatcher to other coroutine to run if possible. This suspending function is cancellable.
Yields a value to the Iterator being built and suspends until the next value is requested.
a coroutine is a function that can suspend its execution before reaching return, and it can indirectly pass control to another coroutine for some time.
On Android, coroutines help to manage long-running tasks that might otherwise block the main thread and cause your app to become unresponsive. Over 50% of professional developers who use coroutines have reported seeing increased productivity.
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
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.
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.@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.
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
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With