Skip to content
Related Articles

Related Articles

Improve Article
Save Article
Like Article

Jobs, Waiting, Cancellation in Kotlin Coroutines

  • Last Updated : 10 Sep, 2021

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. 

Want a more fast-paced & competitive environment to learn the fundamentals of Android?
Click here to head to a guide uniquely curated by our experts with the aim to make you industry ready in no time!

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




// sample kotlin 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) {
  
        }
    }
}

 
 

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




// 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")
        }
    }
}

 

 

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




// 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 {
              // delaying the coroutine by 2sec
            delay(2000)
              
            // canceling/stopping  the coroutine 
            job.cancel()
            Log.d(TAG, "Main Thread is Running")
        }
    }
}

 

 

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




// 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)
    }
}

 

 

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




// 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 active 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)
    }
}

 

 



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




// 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)
    }
}

 

 

The Log Output is as follows:

 

Time Stamps are shown by Oval Circle

 

Log Output

 




My Personal Notes arrow_drop_up
Recommended Articles
Page :

Start Your Coding Journey Now!