Kotlin Coroutine- Sequential, Concurrent and Lazy execution

hasanga lakdinu
6 min readDec 29, 2020

Hi, welcome to another interesting article on Coroutines in Kotlin. In this tutorial , we are going to see various approaches to create and execute suspending functions.

so we will explore the sequential, concurrent, and lazy execution of the code within a coroutine. So before you get in to this, there are a few things that you should already know.

  1. You should know the basics of the coroutine. like what is a coroutine and how to create a coroutine.
  2. You should know about how to implement suspend functions in Kotlin.
  3. You should be familiar with coroutine builders such as launch, async, and runBlocking functions.

if you are okey with those things let’s go!!!

in this article we are going to see below 3 things,

  1. How functions execution within a coroutine(Sequential or parallel)
  2. How to run them in a concurrent manner (execute those functions in parallel instead of sequential).
  3. Then finally we’ll explore how to execute a block of code lazily using coroutine.

okey. let’s jump in to coding,

import kotlinx.coroutines.*fun main() = runBlocking {
println("Main program starts: ${Thread.currentThread().name}")
//code here
println("Main program ends: ${Thread.currentThread().name}")
}
suspend fun getGreeting():String{
delay(2000L)
return "hello"
}

suspend fun getGreeting2(): String{
delay(2000L)
return "world"
}

Let’s go through each line of code one by one. This getGreeting() is a suspending function which returns a greeting in the form of String.

Then we have another suspending function called getGreeting2() which
returns another greeting in the form of String. Now, please note that in both functions we are using a delay of 2 seconds. in here we are trying to pretend to do some work for 2 seconds.

Then we have the main function and in the main function, we are using runBlocking coroutine builder. Which means our entire main function now executes within runBlocking coroutine.

Now let us call these two functions from our main function.

//Change your main function like below
fun main() = runBlocking {
println("Main program starts: ${Thread.currentThread().name}")
val greetingOne= getGreeting()
val greetingTwo= getGreeting2()
println("The entire greeting is: ${greetingOne+greetingTwo}") println("Main program ends: ${Thread.currentThread().name}")
}

So here I will call getGreeting() function and store the returned value within a variable.Similarly, I will call getGreeting2() function and store the returned value within another variable. And then I will print the entire greeting by joining them.

Let us run the program.

Main program starts: main
The entire greeting is: helloworld
Main program ends: main
Process finished with exit code 0

So here we go, this is the output, as expected.

But there is a problem, how these two functions are being executed. Are they getting executed in sequence? Or are they getting executed concurrently? how we overcome this problem?

Well, we can easily know that by checking how much time is elapsed while executing these two functions. For that, let’s use measureTimeMillis function.

Which belongs to kotlin system.measureTimeMillis package.

let’s change the main function like below,

fun main() = runBlocking {
println("Main program starts: ${Thread.currentThread().name}")
val time = measureTimeMillis {
val greetingOne= getGreeting()
val greetingTwo= getGreeting2()
println("The entire greeting is:${greetingOne+greetingTwo}")
}
println("completed in $time ms")
println("Main program ends: ${Thread.currentThread().name}")
}

Now, measureTimeMillis this function is going to measure the time take by this code to execute. and save it in time variable. then print it out.Nice and simple!

Let’s run our program.

Main program starts: main
The entire greeting is: helloworld
completed in 4028 ms //this time can be changed littlebit
Main program ends: main
Process finished with exit code 0

So here we go, it took around 4 seconds to execute the two functions. which means 2 seconds were consumed per each greeting functions.

Which means these two functions executed one after another in sequence.

So here you can see Within a coroutine which in our case is runBlocking,
by default the methods are executed sequentially one after another.

N.B:- codes are sequentially executed within a coroutine by default.

So let’s see how we can execute those functions concurrently(simply means parallel).

Now, if we wrap these getGreeting(), getGreeting2() within async or launch coroutine builders, then let’s see what happens.

//change your main like below
fun main() = runBlocking {
println("Main program starts: ${Thread.currentThread().name}")
val time = measureTimeMillis {
val greetingOne: Deferred<String> = async { getGreeting() }
val greetingTwo: Deferred<String> = async { getGreeting2() }
println("The entire greeting is: ${greetingOne.await()+greetingTwo.await()}")
}
println("completed in $time ms")
println("Main program ends: ${Thread.currentThread().name}")
}

if we create child coroutine using async coroutine builder, then this async function returns a value in the form of deferred object that is whatever is the last statement in this getGreeting functions return are in the form of deferred generic type objects.

from these greetingOne and greetingTwo we can get the exact string values using await function(that what I have done in the println statement).

Let’s run the program.

Main program starts: main
The entire greeting is: helloworld
completed in 2080 ms
Main program ends: main
Process finished with exit code 0

So here we go the output remains almost the same. The only difference you will notice is in the time taken to execute these two functions.Right now it takes only 2 seconds to execute these functions.

So what is happenning under the hood?

Well, this async function is a coroutine builder. It creates another coroutine that runs in the background. Similarly, this async again creates a background
coroutine. And these two coroutines runs in the background at the same time, i.e. in parallel. So using async we have achieved concurrency.

Okey let’s move on to our next topic. i.e. lazily execute the coroutine.

Let’s see how to do it. before we start, let’s do some changes to our program. In our suspend functions, Let’s add a print statement like below.

suspend fun getGreeting():String{
delay(2000L)
println("After working in getGreeting()")
return "hello"
}

suspend fun getGreeting2(): String{
delay(2000L)
println("After working in getGreeting2()")
return "world"
}

So that we can know, these functions are getting executed.

Now coming back to our main function.

//removed the time measuring related things
fun main() = runBlocking {
println("Main program starts: ${Thread.currentThread().name}")
val greetingOne: Deferred<String> = async { getGreeting() }
val greetingTwo: Deferred<String> = async { getGreeting2() }
println("The entire greeting is: ${greetingOne.await()+greetingTwo.await()}")
println("Main program ends: ${Thread.currentThread().name}")
}

in here what we are receiving as a result (greetingOne,greetingTwo) in these two variables, we are using them in print statement. but if we comment this print statement, then basically executing async { getGreeting() } async { getGreeting2() } these two child coroutines are of no use. because we are not using the return result in our programme. they remain unused.

fun main() = runBlocking {
println("Main program starts: ${Thread.currentThread().name}")
val greetingOne: Deferred<String> = async { getGreeting() } val greetingTwo: Deferred<String> = async { getGreeting2() }
//commented out the usages of two variables
// println("The entire greeting is: //${greetingOne.await()+greetingTwo.await()}")
println("Main program ends: ${Thread.currentThread().name}")
}



suspend fun getGreeting():String{
delay(2000L)
println("After working in getGreeting()")
return "hello"
}

suspend fun getGreeting2(): String{
delay(2000L)
println("After working in getGreeting2()")
return "world"
}

but if we run the programme,

Main program starts: main
Main program ends: main
After working in getGreeting()
After working in getGreeting2()
Process finished with exit code 0

we can see that suspending functions are getting executed.(it is wasting system resources without giving any useful result in our application.)

So to avoid this, in Kotlin, we have a workaround for that. We can make our coroutine execute only if we use the result in our program.

For that, as a parameter, just pass, start equal to CoroutineStart.LAZY, let’s see in action.

//change your main method as below
fun main() = runBlocking {
println("Main program starts: ${Thread.currentThread().name}")
val greetingOne: Deferred<String> = async(start = CoroutineStart.LAZY) { getGreeting() }
val greetingTwo: Deferred<String> = async(start = CoroutineStart.LAZY) { getGreeting2() }
// println("The entire greeting is: //${greetingOne.await()+greetingTwo.await()}")
println("Main program ends: ${Thread.currentThread().name}")
}

If you run the program now, you will find that now those two suspending functions were not executed.

//output
Main program starts: main
Main program ends: main

In short, only if you use these values in your program, your coroutine is going to execute these functions.

So now, if we comment out the print statement and run the program again it will give the output accordingly ,

Great isn’t it?.

with that we are coming to the end of this article about kotlin coroutines, I hope this article will help you to get the idea behind Sequential, Concurrent and Lazy execution of coroutines in Kotlin, if you have any doubt please leave a comment. if you enjoy the article please give a clap, okey see you guys in another article until then Happy Coding!!!

--

--