Amit Nadiger
28 Oct 2022
•
6 min read
Co-Routines means cooperative routines . i.e routines which cooperate with each other.
It means when one routine is executing , the other routine will not interfere with 1st coroutine w.r.t to memory , cpu ,any resource by suspending itself i.e without blocking.
Coroutines don't have a dedicated stack. It means coroutine suspend execution by returning to the caller and the data that is required to resume execution is stored separately from the stack. Coroutines share the stack due to support for suspension.
How to convert the function into coroutines. Its very easy in Kotlin - prepend the suspend keyword to the regular function as below :
suspend fun backgroundTask(param: Int): Int {
// long running operation
}
Under-hood conversion of Suspend by the compiler:
fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
// long running operation
}
a new additional parameter of type Continuation
Continuation
CoroutineBuilders help in creating coroutines. Since CoroutineBuilders are not suspending themselves, they can be called from non-suspending code or any other piece of code. They act as a bridge between the suspending and non-suspending world.
Typically, runBlocking used to run tests on suspending functions. During tests, to make sure not to finish the test while we are doing heavy work in test suspend functions.
1.launch creates a new coroutine that won’t return any result to the caller.
2.It also allows to start a coroutine in the background.
fun main() {
GlobalScope.launch {
println(doSomethingHeavy())
---- do something -----
}
runBlocking {
delay(3000L) // Keep JVM alive until coroutine is completed.
}
}
suspend fun doSomethingHeavy() : String {
delay(2000L) // simulate long running heavy task
return "Did some heavy operation that was 2 seconds long"
}
O/P : After 2 sec it prints the string: "Did some heavy operation that was 2 seconds long".
Can be used to perform an asynchronous task which returns a value and achieves parallel execution .
This value in Kotlin terms is a Deferred
We can call await on the deferred value in order to wait and get the result.
Use async for results from multiple tasks that run in parallel.
val userId = 1 // UserId
fun main() {
println(“get userName from Sever ")
GlobalScope.launch {
val userName= async {
getUserNameFromServer(userId)
}
val userAge = async {
getUserNameFromServer(userId)
}
if (userName.await() && userAge.await()) {
println(“UserName of userId($ userId is: ${ userName .await()} with Age = ${userAge.await()} ")
} else {
println("Please wait ,till both userName and userAge are fetched!") }
}
}
println("coroutine is waiting for a result...")
runBlocking {
delay(3000L) // only used to keep the JVM alive
}
}
suspend fun getUserNameFromServer (Int:userId): String ?{ // This is coroutine
var UserName:String? = null
UserName = // Do network call to get user Name based on userId
return UserName
}
suspend fun getUserAge (Int:userId): Int? { // This is coroutine
var UserAge:Int? = null
UserName = // Do network call to get userAge based on userId
return UserAge
}
O/P : Here async builder will suspend the coroutine (getUserNameFromServer () and getUserAge(). async will return a Deferred value as the result of getUserNameFromServer () by calling the suspending function await() on it. await() will fetch us the result that getUserNameFromServer ()/ async returns.
While getUserNameFromServer () is executing, the following happens: async builder will suspend the coroutine (get sername from the web server ).
The execution of other tasks i.e getUserAge() also continues.
Once getUserNameFromServer () returns a userName, userName is stored in the global variable .
Step 1 to 3 will be executed for getUserAge() also.
Once both userName and UserAge is fetched , it will be printed.
Use withContext when you do not need the parallel execution.
Both withContext and async used to get the result which is not possible with the launch.
Use withContext to return the result of a single task
Below example (Copied from the android developer site ):
suspend fun fetchDocs() { // Dispatchers.Main
val result = get("developer.android.com") // Dispatchers.Main
show(result) // Dispatchers.Main
}
suspend fun get(url: String) =  // Dispatchers.Main
withContext(Dispatchers.IO) { // Dispatchers.IO (main-safety block)
/* perform network IO here */   // Dispatchers.IO (main-safety block)
}        // Dispatchers.Main
}
runBlocking and coroutineScope may look similar because they both wait for their body and all its children to complete. The main difference is that the runBlocking method blocks the current thread for waiting, while coroutineScope just suspends, releasing the underlying thread for other usages. Because of that difference, runBlocking is a regular function and coroutineScope is a suspending function.
-- Where (on which thread) to run coroutine.Defines a scope for new coroutines. CoroutineScope is nothing but a CoroutineContext => But intended use of both are different .
CoroutineScope keeps track of any coroutine it creates using launch or async
In Kotlin, all coroutines run inside a CoroutineScope.
Whenever a new coroutine scope is created, a new job gets created and & associated with it.
A scope controls the lifetime of coroutines through its job.
Every coroutine created using this scope becomes the child of this job.(this is parent & child relation in coroutine)
If any of the coroutines throws an unhandled exception, it’s parent job gets canceled. Ex: scope.cancel() . When a parent job is cancelled ultimately cancels all its children. This is called structured concurrency
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
Structured concurrency do below three things:
CoroutineScope is an interface that has a single abstract property called coroutineContext. Every coroutine builder (like launch, async, etc.) is an extension on CoroutineScope and inherits its coroutineContext to automatically propagate all its elements and cancellation.
Ex: On Android, you can use a scope to cancel all running coroutines when, for example, the user navigates away from an Activity or Fragment.
Coroutines always execute in some context that is a set of various elements. Below are main elements of coroutine context
Job – models a cancellable workflow with multiple states and a life-cycle that culminates in its completion. Launch returns a Job object.
When you launch a coroutine, you basically ask the system to execute the code you pass in, using a lambda expression. That code is not executed immediately, but it is, instead, inserted into a queue
A Job is basically a handle to the coroutine in the queue. It only has a few fields and functions, but it provides a lot of extensibility. For instance, it’s possible to introduce a dependency relation between different Job instances using join(). If Job A invokes join() on Job B, it means that the former won’t be executed until the latter has come to completion. It is also possible to set up a parent-child relation between Job instances using specific coroutine builders. A Job cannot complete if all its children haven’t completed. A Job must complete in order for its parent to complete.
Job is essentially a task that runs in background, and it can be interpreted as an action, with a lifecycle that gets destroyed once finished.
You can even establish a hierarchy, with parents and child jobs, so that when you cancel the father, the children also get cancelled.
We can run different operations using jobs:
Job.join blocks the coroutine associated to that job until all the children jobs finish.
scope.launch {
val job = CoroutineScope(Dispatchers.Main).launch {
val foo1 = suspendFoo1()
val foo2 = suspendFoo2()
doSomething(foo1, foo2)
}
job.join()
callOnlyWhenJobAboveIsDone()
}
Here callOnlyWhenJobAboveIsDone() is called only when doSomething() is finished i.e means even suspendFoo1 and suspendFoo2 are finished.#### Job.cancel: Cancels all the children jobs.
Determines what thread or threads the corresponding coroutine uses for its execution. With the dispatcher, we can confine coroutine execution to a specific thread, dispatch it to a thread pool, or let it run unconfined. When I say coroutines can run on more than one thread , how to decide which thread they can run on:
CoroutinesDispatchers comes to rescue:
Dispatchers specify where the coroutines should run i.e Dispatches work to the appropriate thread.#### Dispatchers .Main: suitable for main thread on Android UI and perform light work .#### Dispatchers .IO: Suitable and optimized for Disk and Network IO operations , data base operation ,etc##### Dispatchers .Default: Suitable or optimized for CPU intensive work such as sorting operation etc .
Below is copied from the Kotlin site :
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
launch { // context of the parent, main runBlocking coroutine
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // not confined -- will work with main thread
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // will get dispatched to DefaultDispatcher
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // will get its own new thread
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
}
O/P :
Unconfined : I'm working in thread main
Default : I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread
main runBlocking : I'm working in thread main
Further reading : https://github.com/Kotlin/KEEP I hope you will find this article useful in some way !
If you have any questions or suggestions , please leave get in touch!
Amit Nadiger
See other articles by Amit
Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ
108 E 16th Street, New York, NY 10003
Join over 111,000 others and get access to exclusive content, job opportunities and more!