Jobs, Waiting, Cancellation in Kotlin Coroutines

19 May, 2022
Prerequisite:
Kotlin Coroutines On Android
Dispatchers in Kotlin Coroutines
Suspend Function In Kotlin Coroutines
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. 
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.
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 kotlinimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.GlobalScopeimport 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) {         }    }}
  
 
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() 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 methodimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.util.Logimport 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
 
 
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 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 programimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.util.Logimport 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
 
 
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 programimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.util.Logimport 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
 
 
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 programimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.util.Logimport 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 functionality 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
 
 
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.
 
 
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 programimport androidx.appcompat.app.AppCompatActivityimport android.os.Bundleimport android.util.Logimport 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
 
 
sweetyty
abhishek0719kadiyan
simmytarika5
android
Android
Kotlin
Android
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
How to Add Views Dynamically and Store Data in Arraylist in Android?
Android SDK and it's Components
Flutter - Custom Bottom Navigation Bar
How to Communicate Between Fragments in Android?
Retrofit with Kotlin Coroutine in Android
How to Add Views Dynamically and Store Data in Arraylist in Android?
Android UI Layouts
Kotlin Array
How to Communicate Between Fragments in Android?
Retrofit with Kotlin Coroutine in Android