Coroutine builders & launching coroutines in Kotlin

Coroutine builders

Coroutine builders are functions that help us create a coroutine. They can be called from normal functions because they do not suspend themselves.

The kotlinx.coroutines library provides the three most essential coroutine builders:

  • launch
  • async
  • runBlocking

Let’s explore them one by one.

The launch builder

The launch builder is an essential kotlin coroutine builder. It works on the “fire and forgets” approach meaning that upon launch, a new coroutine will be created, and it will not return anything to its caller. The started coroutine will keep working in the background.

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
fun main() {
GlobalScope.launch {
delay(1000L)
println("First Coroutine")
}
GlobalScope.launch {
delay(1000L)
println("Second Coroutine")
}
GlobalScope.launch {
delay(1000L)
println("Third Coroutine")
}
println("Hello,")
Thread.sleep(2000L)
}

Explanation

Lines 1–3: We import the kotlinx.coroutines packages.

Lines 6–8: The first coroutine will be launched in the background here, but the main function will not stop.

Lines 10–13: The second coroutine will be launched in the background here, but the main function will keep working in the meantime.

Lines 14–17: The third coroutine will be launched in the background here, but the main function will keep working in the meantime.

Line 18: Print the Hello statement, and then First Coroutine, Second Coroutine, and Third Coroutine will be printed in that order.

The asyncbuilder

The async and launch builders are very similar, but unlike launch it returns a Deferred <T> object which is the equivalent of a promise. We can call the suspending function await on the deferred value to wait and get the result.

import kotlinx.coroutines.*
fun main() = runBlocking {
val resultDeferred: Deferred<Int> = GlobalScope.async {
delay(1000L)
return 5
}
val result: Int = resultDeferred.await()
println(resultDeferred.await())
}

In the above example, the returned value is 5 and its datatype is int. The Deffered<int> is returned, and the await function will wait for the result 5.

Similar to the launch builder, async start a coroutine right away. It is a method for starting several processes at once and then wait for the outcome.

The runBlockingbuilder

As the name implies, the runBlocking coroutine builder, blocks the main thread until each of the coroutine's tasks completed, that the main thread is created.

We normally use runBlocking in the main thread when we are running tests to ensure the test doesn't end while doing heavy computation in the test-suspended functions.

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking {
delay(1000L)
println("First Coroutine")
}
runBlocking {
delay(1000L)
println("Second Coroutine")
}
runBlocking {
delay(1000L)
println("Third Coroutine")
}
println("Print Order")
}

Explanation

Lines 1–2: Import the kotlinx.coroutines packages.

Lines 5–7: The first coroutine will be launched here, and the First Coroutine will be printed with a 1-second delay.

Lines 9–11: The second coroutine will be launched here, and Second Coroutine will be printed with a 1-second delay.

Lines 13 – 15: The third coroutine will be launched here, and Third Coroutine will be printed with a 1-second delay.

Line 17: Print Order statement will be printed in last.

Launching Coroutines

Coroutines are launched in coroutine builders and are bound by a coroutine scope. A lazy way to launch a coroutine would be to use the GlobalScope. This means the coroutine will be running for as long as the application is running, and if there is no important reason to do this, it’s a way to misuse resources.

In launching coroutines, we would use the launch keyword like this:

...
GlobalScope.launch{
doSomething() // does some heavy computation in the background
... do other stuff
}
...
suspend fun doSomething(){
// heavy computation here
}

However, as stated earlier, we don’t want to launch coroutines in the GlobalScope. When launching a coroutine, we want to be sure that we do it right to avoid coroutine leaks that will cause us to run out of memory.

Launching coroutines in GlobalScope, and forgetting to keep a reference so that we can manually cancel later, could be as error-prone as using callbacks. How do we solve this problem? That’s where structured concurrency comes in.