Kotlin Coroutines

Coroutines are nothing but lightweight threads. Coroutines provide us an easy way to do synchronous and asynchronous programming. Coroutines allow execution to be suspended and resumed later at some point in the future which is best suited for performing non-blocking operations in the case of multithreading.

Coroutines is a lightweight thread because creating coroutines doesn't allocate new threads. Instead, they use predefined thread pools, and smart scheduling. Scheduling is the process of determining which piece of work you will execute next.. Is called on suspending functions. We can suspend and resume the Coroutines while execution. This means we can have a long-running task, which can be executed one by one or  which can be executed little-by-little.. We can pause it any number of times and resume it when we are ready again.

Advantages/Properties :

  • Run 1 background thread & Multiple Coroutines
  • Light Weight Threads
  • Can run in parallel
  • Can wait for each other
  • Communicate with each other
  • Coroutines != thread
  • Threads doesn't have such inbuilt mechanism to cancel itself internally
  • Can create 100's of coroutines without any memory issue
  • For threads, application waits for the background thread to complete its execution whereas in case of Coroutines the application will not have any idea about coroutines
  • If we are calling any coroutine from a fragment or activity using lifecycleScope.launch(), whenever the activity or fragment is destroyed, the coroutine also gets destroyed
  • Lower chances for memory leaks
  • Built-in cancellation support
  • Jetpack libraries provide coroutines support

Coroutine Scope

A CoroutineScope keeps track of any coroutine it creates using the builder functions launch or async. It provides the ability to cancel a coroutine at any point in time. The scope is nothing but a lifespan. A coroutine cannot be launched without scope. CoroutineScope gets notified whenever a failure happens. CoroutineScope takes CoroutineContext as an argument. A CoroutineScope keeps track of any coroutine it creates using the builder functions launch or async. It provides the ability to cancel a coroutine at any point in time. The scope is nothing but a lifespan. A coroutine cannot be launched without scope. CoroutineScope gets notified whenever a failure happens. CoroutineScope takes CoroutineContext as an argument.

Different Types of Coroutine Scopes :
  • LifecycleScope : This makes sure that all the coroutines running in that particular activity/fragment are destroyed when that particular activity/fragment is destroyed
  • ViewModelScope : It will keep the coroutine alive as long as the ViewModel is alive 

  • GlobalScope : Lifecycle of this scope is equal to the lifecycle of the entire application and causes memory leaks. To be used only while debugging and not in production applications.  GlobalScope.launch { }
  • Use Dispatchers ie GlobalScope.launch(Dispatchers.Main) { } to make Main Safe
  • We should always create a scope which automatically gets destroyed whenever its not needed so that it wont cause any memory leak ie. viewLifecycleOwner.lifecycleScope.launch {  //suspending function }

CoroutineContext


CoroutineContext is nothing but an interface.  It is an indexed set of Element instances. An indexed set is a mix between a set and a map. Every element in this set has a unique

A CoroutineContext defines the behavior of a coroutine using the following set of elements:
Job: Controls the lifecycle of the coroutine.
CoroutineDispatcher: Dispatches work to the appropriate thread.
CoroutineName: The name of the coroutine, useful for debugging.
CoroutineExceptionHandler: Handles uncaught exceptions.

Dispatchers

Dispatchers specify on which thread the operation should be performed.
Dispatchers help coroutines in deciding the thread on which the work has to be done. Dispatchers are passed as the arguments to the GlobalScope by mentioning which type of dispatchers we can use depending on the work that we want the coroutine to do.

If we have to do a network call and update the UI then we need to change the context of the coroutine. i.e. we need to use Dispatchers.IO to make the network call and Dispatchers.Main to update the UI based on the results of the network call.
GlobalScope.launch(Dispatchers.IO) {
    val result = doNetworkCall()
    withContext(Dispatchers.Main) {
        updateUI()
    }
}

Dispatchers.IO - Used for Network Request or Disk read write. Used for Data Operations like Networking, Database operations, Reading or writing to Files

Dispatchers.Main - For Main Safety, Starts the coroutine on the main thread.
Useful if we have to do any UI operations within the coroutine.
It is confined to the Main thread operating with UI objects. This dispatcher can be used either directly or via the MainScope factory. Usually, such dispatcher is single-threaded.

Dispatchers.Default - CPU Intensive Tasks. Used if we have to do long running or complex calculations that may block the main thread
Eg : If we have to sort a list of 10000 list of elements

withContext
Inside a coroutine, we use withContext to switch between Dispatchers. It’s nothing but a suspend function. It calls the specified suspending block with a given coroutine context, suspends until it completes, and returns the result. This suspending function is cancellable. It immediately checks for cancellation of the resulting context and throws CancellationException if it is not active

Suspending Functions :

A function that may be started, halted, then resumed is known as a suspend function. One of the most important things to remember about the suspend functions is that they can only be invoked from another suspend function or from a coroutine. Suspending functions are merely standard Kotlin functions with the suspend modifier added, indicating that they can suspend coroutine execution without blocking the current thread. This means that the code you're looking at may pause execution when it calls a suspending function and restart execution at a later time.

Suspending functions can call any other ordinary functions, but another suspending function is required to suspend the execution. Because a suspending function cannot be called from a regular function, numerous coroutine builders are supplied, allowing you to call a suspending function from a non-suspending scope like launch, async, or runBlocking.
delay() function is an example of suspend function
runBlocking {
    delay(1000)
}

Job
A Job is a handle to a coroutine. A job is a cancellable thing with a life cycle that ends on its completion or cancellation or failure.
Each coroutine that we create with launch or async returns a Job object that uniquely identifies the coroutine and manages its lifecycle. If we use a normal Job instance when there was an exception in any child other than CancellationException it cancels the parent as well as other children.
The statement job.cancel() just cancels the coroutine started above, this doesn’t affect the scope.
A job is in the active state while the coroutine is working or until the job is completed, or until it fails or canceled.

Why launch is called fire and forget ??

The launch coroutine builder does a fire and forget, it does not return any result to the caller asides the job instance which is just a handle to the background operation. 

It inherits the context and job from the scope where it was called but these can be overridden

Fire and forget means it won't return the result to the caller. The coroutineScope builder will suspend itself until all coroutines started inside of it are complete

Launch and Async Parallel Execution scenario : 
The below code can be used to call the coroutines paralelly using launch operation but its a tideous job to do
val TAG = "MainActivity"
val job = GlobalScope.launch(Dispatchers.IO) {
val time = measureTimeMillis {
var result1 : String? = null
var result2 : String? = null
val job1 = launch { result1 = networkCall1() }
val job2 = launch { result2 = networkCall2() }
job1.join()
job2.join()
Log.d(TAG, "Answer1 is $result1")
Log.d(TAG, "Answer2 is $result2")
}
Log.d(TAG, "Time taken $time")
}
Using Async and Await:
The 
async call will return a Deferred result. The deferred type depends on what the async block is returning. If we need to do something asynchronously and need the result of it then we need to make use of it and to get the result we need to call the await() method and this will block the coroutine until the result is available
val TAG = "MainActivity"
val job = GlobalScope.launch(Dispatchers.IO) {
val time = measureTimeMillis {
var result1 = async { networkCall1() }
var result2 = async { networkCall2() }
Log.d(TAG, "Answer1 is ${result1.await()}")
Log.d(TAG, "Answer2 is ${result2.await()}")
}
Log.d(TAG, "Time taken $time")
}

Difference between launch and async

launch / join:-
The launch command is used to start and stop a coroutine. It's as though a new thread has been started. If the code inside the launch throws an exception, it's considered as an uncaught exception in a thread, which is typically written to stderr in backend JVM programs and crashes Android applications. Join is used to wait for the launched coroutine to complete before propagating its exception. A crashed child coroutine, on the other hand, cancels its parent with the matching exception.

async / await:-
The async keyword is used to initiate a coroutine that computes a result. We must use await on the result, which is represented by an instance of Deferred. Uncaught exceptions in async code are held in the resultant Deferred and are not transmitted anywhere else. They are not executed until processed


The launch{} does not return anything and the async{} returns an instance of Deferred, which has an await() function. In other words, we can say that launch is used to fire and forget, and async is used to perform a task and return a result.

launch is used to fire and forget coroutine. It is like starting a new thread. If the code inside the launch terminates with exception, then it is treated like uncaught exception in a thread -- usually printed to stderr in backend JVM applications and crashes Android applications. 

join is used to wait for completion of the launched coroutine and it does not propagate its exception. However, a crashed child coroutine cancels its parent with the corresponding exception, too.

async is used to start a coroutine that computes some result. The result is represented by an instance of Deferred and you must use await on it. 

An uncaught exception inside the async code is stored inside the resulting Deferred and is not delivered anywhere else, it will get silently dropped unless processed. You MUST NOT forget about the coroutine you’ve started with async.

1. Async returns a value which is a Deferred which has the value and to get the value from the Deferred, we need to use AWAIT on it and this is when we get the actual value
eg : 
viewModelScope.launch {
    students as MutableLiveData
    val students = async {
        getStudents() 
    }
    val data = students.await() // Here the data will have the List of Students
}

1. Launch doesn't return a value. The method launch follows is Fire and Forget
eg : 
viewModelScope.launch {
    students as MutableLiveData
    students.value = getStudents()
}

2. Launch executes sequentially
eg : 
    viewModelScope.launch {
    getStudents()
    getStudents()
    
    getColleges() 
    getColleges() 
}

2. Async executes parallelly
eg : 
viewModelScope.launch {
    val students1 = async { getStudents() }
    val students2 = async { getStudents() }

    val colleges1 = async { getColleges() }
    val colleges2 = async { getColleges() }
}


Exception Handling in Kotlin Coroutines
We need to take care of exceptions and handle them with try/catch or by providing CoroutineExceptionHandler.

In the case of using launch coroutine builder, the exception will be thrown immediately. So it would be best to wrap it with try/catch
scope.launch {
try {
doSomething()
} catch(e: Exception) {
// Handle exception logic
}
}

However, in the case of async, it’s not the case. It holds the exception until await is invoked. So we can wrap the .await() call inside try/catch

viewModelScope {
val deferredResult = async {
doSomething()
}
try {
deferredResult.await()
} catch(e: Exception) {
// Handle exception thrown
}
}



What are Coroutine Builders?

Coroutine builders are simple extension functions that can create and start a coroutine

Coroutine builders are a way of creating coroutines. Since they are not suspending themselves, they can be called from non-suspending code or any other piece of code. They act as a link between the suspending and non-suspending parts of our code.


The following are three coroutine builders:

1. runBlocking

2. launch

3. async

RunBlocking :     

runBlocking blocks the current thread on which it is invoked until the coroutine is completed. It runs the coroutine in the context on the thread it is invoked. It throws InterruptedException If the blocked thread is interrupted

We typically runBlocking to run tests on suspending functions. While running tests, we want to

make sure not to finish the test while we are doing heavy work in test suspend functions.

Launch :

launch is essentially a Kotlin coroutine builder that is “fire and forget”. This means that launch

creates a new coroutine that won’t return any result to the caller. It also allows to start a

coroutine in the background.

The simplest way to create a coroutine is by calling the launch builder on a specified scope. It Launches a new coroutine without blocking the current thread and returns a reference to the coroutine as a Job. The coroutine is canceled when the resulting job is canceled. 

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
println("Starting main function..")

GlobalScope.launch {
println(doSomething())
}
runBlocking {
delay(4000L) // make sure to keep the JVM alive in order to wait for doSomething() to execute
}
}

suspend fun doSomething() : String {
delay(3000L) // simulate long running task
return "Did something that was 3 seconds long"
}

In the above code, we call the suspending function, doSomething() and after 3 seconds of delay we print the string: “Did something that is 3 seconds long”.

Note: “delay” in Kotlin, is a suspend function which delays a coroutine without blocking the current thread. It then resumes the coroutine after the specified time (In our case, 3 seconds or 3000 milliseconds).

Async-Await()
Async is a coroutine builder which returns some value to the caller. async can be used to perform an asynchronous task which returns a value. This value in Kotlin terms is a Deferred<T> value.
We can simply use async to create a coroutine doing some heavy work
await() is a suspending function that is called upon the async builder to fetch the value of the Deferred object that is returned. The coroutine started by async will be suspended until the result is ready. When the result is ready, it is returned and the coroutine resumes.
We can’t use async in a normal function as it has to call suspend function await to get the result. So we generally use launch builder inside a normal function and then use async inside it. We need to generally use async only when we need the parallel execution of different tasks as it waits for all the tasks to be completed and returns a deferred result
import kotlinx.coroutines.*

val a = 10
val b = 20

fun main() {
println("Gone to calculate sum of a & b")

GlobalScope.launch {
val result = async {
calculateSum()
}
println("Sum of a & b is: ${result.await()}")
}
println("Carry on with some other task while the coroutine is waiting for a result...")
runBlocking {
delay(3000L) // keeping jvm alive till calculateSum is finished
}
}

suspend fun calculateSum(): Int {
delay(2000L) // simulate long running task
return a + b
}


Coroutine Builders : 

Launch, Async & RunBlocking

launch(local scope) : 

Login Operation or logic computation

viewModelScope.launch {

}

GlobalScope.launch{}(Global/Application Level)(Like file download, play music which happens in background) 

Not much preferred cos if we miss the reference to it then it will be a problem as it will run in the background and consumes a lot of memory

async(local scope) :

GlobalScope.async{}(Global/Application Level)

runBlocking : 

runBlocking{}

Global coroutines are top level coroutines which can survive the entire life of the application

If the runBlocking is running in the main thread or thread T1 then the launch also runs on the same thread and acts as the child coroutine

i.e. it inherits the property of the immediate coroutine and returns a job object

Job Object : 

Used to control the coroutine

Launch creates a coroutine that runs on a thread

On the same thread there could be many coroutines that run on the same thread

launch coroutine builder is like fire and forget

when delay function is executed, it suspends the current coroutine and the thread remains free for the time period and can be utilized by other coroutine to run on this thread

It doesn't block the thread on which it operates

It inherits the thread and coroutine scope of the immediate parent coroutine

Returns the job object 

controls the coroutine

Async Coroutine Builders

Async doesnt return a job object. It returns a Deffered object which is a Generic type which is a subclass of job object

If we are not returning anything then we can go with deferred.join itself and if we are returning anything then we need to use deferred.await()

val data = deferred.await()

join() and await() are suspending functions. Also the delay function

The main difference between launch and async is that we can return some data and retrieve the same using await method

Async Coroutine Builders launches  new coroutine without blocking the current thread and inherits the thread and coroutine scope from immediate parent coroutine

returns a reference to Deferred<T> object

Using deferred object we can either cancel a coroutine or wait for coroutine to finish or retrieve the returned result

runBlocking is generally used to test the suspending functions. It blocks the current thread in which it is operating

Launch and Async coroutines never blocks the thread in which its running

Comments

Popular posts from this blog

Android - Using KeyStore to encrypt and decrypt the data

Stack and Queue

Java Reflection API