Jobs, Waiting, Cancellation in Kotlin Coroutines

Prerequisite:

In this article, the following topics will be discussed like what are the jobs in a coroutine, how to wait for the coroutine, and how to cancel the coroutine. Whenever a new coroutine is launched, it will return a job. The job which is returned can be used in many places like it can be used to wait for the coroutine to do some work or it can be used to cancel the coroutine. The job can be used to call many functionalities like the join() method which is used to wait for the coroutine and the cancel() method which is used to cancel the execution of the coroutine. 

Definition of Job

According to the official documentation, the definition of a job is given as follows:

A Job is a cancellable thing with a life-cycle that culminates in its completion.Coroutine job is created with launch coroutine builder. It runs a specified block of code and completes on completion of this block.

How to Get the Job?

As it is discussed that a job will be returned when a new coroutine will be launched, so now let’s see programmatically how the job is returned and how the job can be used.



Kotlin

filter_none

edit
close

play_arrow

link
brightness_4
code

// sample koltin program in kotlin
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
  
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
          
        // A job is returned
        val job = GlobalScope.launch(Dispatchers.Default) {
  
        }
    }
}

chevron_right


Things Which Can Done Using Job

Coroutines can be controlled through the functions that are available on the Job interface. Some functions out of many that job interface offers are as follows:

  • start()
  • join()
  • cancel()

join() Method

join() function is a suspending function, i.e it can be called from a coroutine or from within another suspending function. Job blocks all the threads until the coroutine in which it is written or have context finished its work. Only when the coroutine gets finishes, lines after the join() function will be executed. Let’s take an example that demonstrates the working of the join() function.

Kotlin

filter_none

edit
close

play_arrow

link
brightness_4
code

// sample kotlin program for demonstrating job.join method
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
  
class MainActivity : AppCompatActivity() {
    
    val TAG:String = "Main Activity"
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
          
        // creating/launching a coroutine will return the job
        val job = GlobalScope.launch(Dispatchers.Default) {
            repeat(5)
            {
                Log.d(TAG, "Coroutines is still working")
                // delay the coroutine by 1sec
                delay(1000)
            }
        }
  
        runBlocking {
              // waiting for the coroutine to finish it's work
            job.join()
            Log.d(TAG, "Main Thread is Running")
        }
    }
}

chevron_right


The Log Output is as follows:

Time Stamps are shown by Oval Circle

Log Output

It can be seen that the Log Statement is not allowed to execute until that coroutine which is running finishes its work and it happens possibly only due to the join() method.



cancel() Method

cancel() method is used to cancel the coroutine, without waiting for it to finish its work. It can be said that it is just opposite to that of the join method, in the sense that join() method waits for the coroutine to finish its whole work and block all other threads, whereas the cancel() method when encountered, kills the coroutine ie stops the coroutine. Let’s take an example that demonstrates the working of the cancel() function.

Kotlin

filter_none

edit
close

play_arrow

link
brightness_4
code

// sample kotlin program
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
  
class MainActivity : AppCompatActivity() {
    
    val TAG:String = "Main Activity"
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        val job = GlobalScope.launch(Dispatchers.Default) {
            repeat(5)
            {
                Log.d(TAG, "Coroutines is still working")
                delay(1000)
            }
        }
  
        runBlocking {
              // dealaynig the coroutine by 2sec
            delay(2000)
              
            // canceling/stopping  the coroutine 
            job.cancel()
            Log.d(TAG, "Main Thread is Running")
        }
    }
}

chevron_right


The Log Output is as follows:

Time Stamps are shown by Oval Circle

Log Output

Canceling a coroutine is not always easier as it is in the above example. One should remember that when someone is using the cancel() method, coroutine should be aware that the cancel method would be encountered, ie it might happen that the cancel method would have encountered and coroutine is still running. In short, there needs to be enough time to tell the coroutine that it has been canceled. The delay() function within the repeat function insure that the coroutine has enough time to prepare Let’s take an example and try to understand these paragraph:

Kotlin

filter_none

edit
close

play_arrow

link
brightness_4
code

// sample kotlin program 
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
  
class MainActivity : AppCompatActivity() {
    
    val TAG:String = "Main Activity"
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        val job = GlobalScope.launch(Dispatchers.Default) {
            Log.d(TAG,"Starting the long calculation...")
              
            // running the loop from 30 to 40
            for(i in 30..40)
            {
                Log.d(TAG, "Result for i =$i : ${fib(i)}")
            }
            Log.d(TAG, "Ending the long calculation...")
        }
  
        runBlocking {
            delay(2000)
            job.cancel()
            Log.d(TAG, "Main Thread is Running")
        }
    }
      
    // fibonacci function
    fun fib(n:Int):Long
    {
        return if(n==0) 0
        else if(n==1) 1
        else fib(n-1) + fib(n-2)
    }
}

chevron_right


The Log Output is as follows:

Time Stamps are shown by Oval Circle

Log Output



It can be seen that even after the cancel() method has been encountered, our coroutine will continue to calculate the result of the Fibonacci of the numbers. It is so because our coroutine was so busy in doing the calculation that it does not get time to cancel itself. Suspending there has no suspended function(like delay()), we don’t have enough time to tell the coroutine that it has been canceled. So we have to check manually that if the coroutine has been canceled or not. This can be done using isActive to check whether the coroutine is active or not. 

Kotlin

filter_none

edit
close

play_arrow

link
brightness_4
code

// sample kotlin program 
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
  
class MainActivity : AppCompatActivity() {
    
    val TAG:String = "Main Activity"
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        val job = GlobalScope.launch(Dispatchers.Default) {
            Log.d(TAG, "Starting the long calculation...")
            for(i in 30..40)
            {
                  // using isActive fuctionality to check whether the
                  // coroutine is acitve or not
                if(isActive)
                Log.d(TAG, "Result for i =$i : ${fib(i)}")
            }
            Log.d(TAG, "Ending the long calculation...")
        }
  
        runBlocking {
            delay(2000)
            job.cancel()
            Log.d(TAG, "Main Thread is Running")
        }
    }
  
    fun fib(n:Int):Long
    {
        return if(n==0) 0
        else if(n==1) 1
        else fib(n-1) + fib(n-2)
    }
}

chevron_right


The Log Output is as follows:

Time Stamps are shown by Oval Circle

Log Output

It can be seen that using isActive has increased the awareness of coroutine towards its cancellation and it performs very less calculation after being canceled, as compared to that when isActive has not been used.

withTimeOut() 

Kotlin coroutine has come up with a good solution to the above problems i.e, the coroutine will automatically be canceled and don’t do any further calculations when a certain time has elapsed and withTimeOut() helps in doing so. There is no need to cancel the coroutine manually by using runBlocking() function. Let’s see how withTimeOut() function works:

Kotlin

filter_none

edit
close

play_arrow

link
brightness_4
code

// sample kotlin program
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.coroutines.*
  
class MainActivity : AppCompatActivity() {
    
    val TAG:String = "Main Activity"
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
  
        val job = GlobalScope.launch(Dispatchers.Default) {
            Log.d(TAG, "Starting the long calculation...")
              
            // using withTimeOut function
            // which runs the coroutine for 3sec
            withTimeout(3000L)
            {
                for(i in 30..40)
                {
                    if(isActive)
                        Log.d(TAG, "Result for i =$i : ${fib(i)}")
                }
            }
            Log.d(TAG, "Ending the long calculation...")
        }
  
    }
  
    fun fib(n:Int):Long
    {
        return if(n==0) 0
        else if(n==1) 1
        else fib(n-1) + fib(n-2)
    }
}

chevron_right


The Log Output is as follows:

Time Stamps are shown by Oval Circle

Log Output




My Personal Notes arrow_drop_up

Check out this Author's contributed articles.

If you like GeeksforGeeks and would like to contribute, you can also write an article using contribute.geeksforgeeks.org or mail your article to contribute@geeksforgeeks.org. See your article appearing on the GeeksforGeeks main page and help other Geeks.

Please Improve this article if you find anything incorrect by clicking on the "Improve Article" button below.


Article Tags :
Practice Tags :


1


Please write to us at contribute@geeksforgeeks.org to report any issue with the above content.