Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the purpose of coroutine yield()?

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?

like image 504
j2emanue Avatar asked Mar 03 '19 09:03

j2emanue


People also ask

What does coroutine Yield do?

Yields the thread (or thread pool) of the current coroutine dispatcher to other coroutine to run if possible. This suspending function is cancellable.

What does yield do in Kotlin?

Yields a value to the Iterator being built and suspends until the next value is requested.

What is the point of a coroutine?

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.

What is the purpose of coroutine in Android?

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.


Video Answer


6 Answers

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

  1. It temporarily deprioritises the current long running CPU task, giving other tasks a fair opportunity to run.
  2. Checks whether the current job is cancelled, since otherwise in a tight CPU bound loop, the job may not check until the end.
  3. Allows for progress of child jobs, where there is contention because more jobs than threads. This may be important where the current job should adapt based on progress of other jobs.
like image 145
Yuri Schimke Avatar answered Oct 11 '22 21:10

Yuri Schimke


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.

like image 33
j2emanue Avatar answered Oct 11 '22 21:10

j2emanue


I would answer the question in the context of 4 related things:

  • Sequence 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.
  • Coroutine 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.
like image 24
Xuan Avatar answered Oct 11 '22 21:10

Xuan


@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.

like image 35
Abhijit Sarkar Avatar answered Oct 11 '22 23:10

Abhijit Sarkar


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:

  1. application jump over job = launch get to job.join() and understand that the future code is waiting that job = launch' will finish

  2. 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

  3. get to delay(Long.MAX_VALUE) it's a trap

  4. 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") .

  5. 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

like image 43
Vasile Avatar answered Oct 11 '22 21:10

Vasile


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
like image 31
oiyio Avatar answered Oct 11 '22 23:10

oiyio