Skip to content

How Job in coroutine works

Devrath edited this page Jan 15, 2024 · 16 revisions

Defining the coroutine job

  • A coroutine returns a Job
  • Using the Job we can create control the coroutine like stopping it, So a Job on a coroutine is a handle to the coroutine.
  • More flexibility can be achieved using jobs, we can combine the instances of jobs using join().
  • Say Job-A invokes join on Job-B the former won't be executed until later has finished on execution.
  • We can also set up a parent and child relation using which we can ensure parent won't complete until the child has completed its execution.

States of Jobs

  • New-State:-> When you launch a co-routine, you can create a job, It returns a new state.
  • Active-State:->
    • Once the coroutine is started the job goes from new-state into active-state by default.
    • If you have started the co-routine by LAZY then you need to invoke start() or join() to make the job go from new-state into active-state.
    • A running co-routine is always in active-state. At any point, a running co-routine can be canceled or completed.
    • We can check if a job is in active-state.
    • We can also check by looping the Job to check its children and do something for a particular state of the child.
  • Cancelling-State:->
    • Once you launch a coroutine, many things can possibly happen
      • Exception might occur
      • Because of some new condition, We might need to cancel the job.
    • Usually the uncaught exception causes the entire program to crash, but since the coroutines have suspending behavior, the exception can also be suspended and handled or managed later
    • We can cancel a job by calling cancel() on the job's reference, The job along with all its children's jobs are also canceled.
    • If a Job has a parent and the job is canceled, The parent job is also canceled.

* The `supervisor job` is a special job, where we don't need all the children's jobs to complete for a parent job to complete.
* If we have certain conditions of our own for checking the child scope of the job, We should append it with `isActive()` just to be on the safer side.

Delay() in Action

  • Using the delay function inside the coroutine we can make the coroutine wait for a certain amount of time.
  • Delay is much different because the thread is not blocked instead it's just suspended.
  • Meaning a coroutine that is suspended with delay can be canceled.

Retry() in Action

  • Using the retry mechanism we can loop the code inside the coroutine context a certain number of times mentioned as input.
  • This is helpful in writing complex retry() mechanism sometimes useful in when writing network request.
private fun retryDemo(){
        GlobalScope.launch {
            repeat(3){
                delay(200)
                println("Execution happened")
            }
        }

        /** Output: 
         * Execution happened
         * Execution happened
         * Execution happened
         */
    }

Sample One

Here there is dependency in job instances

 private fun simpleJobDemo() {

        /**
         * This since executed by Lazy, It will start only when invoked
         */
        val job1 = GlobalScope.launch(start=CoroutineStart.LAZY) {
            delay(200)
            println("Two")
            delay(200)
        }

        GlobalScope.launch {
            delay(200)
            println("One")
            // Job is invoked here so it is executed here
            job1.join()
            println("Three")
        }

        /** OUTPUT:
         *
         * One
         * Two
         * Three
         */
}

Sample Two

Passing parent job to the coroutine context, Here There is a parent-child hierarchy. We can clearly see that parent job has the children in it

private fun simpleJobTwoDemo() {

       val rootJob = GlobalScope.launch(start = CoroutineStart.DEFAULT){

           val parentJob = launch {
               delay(10)
               println("Parent job started")
               delay(10)
           }

           val childOneJob = launch(context = parentJob) {
               delay(10)
               println("Child one job started")
               delay(10)
           }

           val childTwoJob = launch(context = parentJob) {
               delay(10)
               println("Child two job started")
               delay(10)
           }

           if(parentJob.children.iterator().hasNext()){
               val childCnt = parentJob.children
               println("Parent has $childCnt children")
           }else{
               println("Parent has no children")
           }

           Thread.sleep(1000)
       }

        /**
         * OUTPUT:->
         * Parent has kotlin.sequences.SequencesKt__SequenceBuilderKt$sequence$$inlined$Sequence$1@85caa11 children
         * Parent job started
         * Child two job started
         * Child one job started
         */
}

Sample Three

Without passing parent job to the coroutine context

  private fun simpleJobTwoDemo() {

       val rootJob = GlobalScope.launch(start = CoroutineStart.DEFAULT){

           val parentJob = launch {
               delay(10)
               println("Parent job started")
               delay(10)
           }

           val childOneJob = launch() {
               delay(10)
               println("Child one job started")
               delay(10)
           }

           val childTwoJob = launch() {
               delay(10)
               println("Child two job started")
               delay(10)
           }

           if(parentJob.children.iterator().hasNext()){
               val childCnt = parentJob.children
               println("Parent has $childCnt children")
           }else{
               println("Parent has no children")
           }

           Thread.sleep(1000)
       }

        /**
         * OUTPUT:->
         * Parent has no children
         * Parent job started
         * Child two job started
         * Child one job started
         */

}
Clone this wiki locally