Coroutine builders

This is a chapter from the book Kotlin Coroutines. You can find Early Access on LeanPub.

Suspending functions need to pass continuations to one another. They do not have any trouble calling normal functions, but normal functions cannot call suspending functions.

Every suspending function needs to be called by another suspending function, which is called by another suspending function, and so on. This all needs to start somewhere. It starts with a coroutine builder, a bridge from normal to the suspending world1.

There are three essential coroutine builders provided by kotlinx.coroutines library we are going to explore:

  • launch
  • runBlocking
  • async

Each has its own use cases. Let's explore them.

launch builder

The way how launch works is conceptually similar to starting a new thread (thread function). We just start a coroutine, and it will run independently. Like a rocket that is launched into space. This is how we use launch - to start some process.

import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch //sampleStart fun main() { GlobalScope.launch { delay(1000L) println("World!") } GlobalScope.launch { delay(1000L) println("World!") } GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) } // Hello, // (1 sec) // World! // World! // World! //sampleEnd

The launch function is an extension function on the interface CoroutineScope. This is a part of an important mechanism called structured concurrency, with the purpose of building a relationship between parent coroutine and child coroutine. Later in this chapter, we will learn all about structured concurrency and also why it is discouraged to use GlobalScope as a coroutine scope.

Another thing that you might have noticed, is that at the end of the function, we need to call Thread.sleep. Without it, our main function would end immediately after launching the coroutines, so they wouldn't have a chance to do their job. It is because delay is not blocking the thread, it is suspending a coroutine. You might remember from the chapter How does suspension work?, that delay just sets a timer to resume after a set amount of time, and suspends a coroutine until then. If the thread is not blocked, nothing is busy, so also nothing stops the program from finishing. Later we will see that Thread.sleep is not needed when we use structured concurrency.

To some degree, the way how launch works is similar to a daemon thread2 but much cheaper. This metaphor is useful at the start, but problematic later. Maintaining a blocked thread is always costly, maintaining a suspended coroutine is almost free (as explained in the chapter Coroutines under the hood). They both start some independent process and need something that will stop the program from ending before they are done (in the below example it is Thread.sleep(2000L)).

import kotlin.concurrent.thread //sampleStart fun main() { thread(isDaemon = true) { Thread.sleep(1000L) println("World!") } thread(isDaemon = true) { Thread.sleep(1000L) println("World!") } thread(isDaemon = true) { Thread.sleep(1000L) println("World!") } println("Hello,") Thread.sleep(2000L) } //sampleEnd

runBlocking builder

The general rule is that coroutines should never block threads, only suspend them. On the other hand, there are cases where blocking is necessary. Like in the main function, we need to block the thread, or otherwise, our program will end too early. For such cases, we might use runBlocking.

runBlocking is a very atypical builder. It blocks the thread it has been started on, whenever its coroutine is suspended3 (similar to suspending main). This means that delay(1000L) inside runBlocking will behave like a Thread.sleep(1000L).

import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking //sampleStart fun main() { runBlocking { delay(1000L) println("World!") } runBlocking { delay(1000L) println("World!") } runBlocking { delay(1000L) println("World!") } println("Hello,") } // (1 sec) // World! // (1 sec) // World! // (1 sec) // World! // Hello, //sampleEnd
import kotlin.* //sampleStart fun main() { Thread.sleep(1000L) println("World!") Thread.sleep(1000L) println("World!") Thread.sleep(1000L) println("World!") println("Hello,") } //sampleEnd

There are actually a couple of specific use cases for runBlocking where it's used. The first one is the main function, where we need to block the thread because otherwise, the program will end. Another common use case is unit tests, where we need to block the thread for the same reason.

fun main() = runBlocking { // ... } class MyTests { @Test fun `a test`() = runBlocking { } }

We might use it in our launch example, to replace Thread.sleep(2000) with delay(2000). Later we will see that runBlocking is even more useful once we introduce structured concurrency.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { GlobalScope.launch { delay(1000L) println("World!") } GlobalScope.launch { delay(1000L) println("World!") } GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") delay(2000L) // still needed } // Hello, // (1 sec) // World! // World! // World! //sampleEnd

runBlocking used to be an important builder, but in modern programming, it is used rather rarely. In unit tests, we often instead use its successor runBlockingTest, which makes coroutines operate on a virtual time (a very useful feature for testing, we will describe it in the chapter Testing coroutines). For the main function, we often make it suspending.

import kotlinx.coroutines.* //sampleStart suspend fun main() { GlobalScope.launch { delay(1000L) println("World!") } GlobalScope.launch { delay(1000L) println("World!") } GlobalScope.launch { delay(1000L) println("World!") } println("Hello,") delay(2000L) } // Hello, // (1 sec) // World! // World! // World! //sampleEnd

Suspending main is convenient, but for now, we will keep using runBlocking4.

async builder

The async coroutine builder is similar to launch, but it is designed to produce some value. This value needs to be returned by the lambda expression5. async function returns an object of type Deferred<T>, where T is the type of the produced value. Deferred has a suspending method await, that returns this value once it is ready. In the below example, the produced value is 42, its type is Int, so Deferred<Int> is returned, and await returns 42 of type Int.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { val resultDeferred: Deferred<Int> = GlobalScope.async { delay(1000L) 42 } // do other stuff... val result: Int = resultDeferred.await() println(result) // 42 // or just println(resultDeferred.await()) // 42 } //sampleEnd

Just like the launch builder, async starts a coroutine immediately when it is called. So it is a way to start a few processes at once and then await their results together. The returned Deferred stores a value inside once it is produced, so once it is ready, it will be immediately returned from await. Although if we call await before the value is produced, we are suspended until the value is ready.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { val res1 = GlobalScope.async { delay(1000L) "Text 1" } val res2 = GlobalScope.async { delay(3000L) "Text 2" } val res3 = GlobalScope.async { delay(2000L) "Text 3" } println(res1.await()) println(res2.await()) println(res3.await()) } // (1 sec) // Text 1 // (2 sec) // Text 2 // Text 3 //sampleEnd

The way how the async builder works is very similar to launch, but it has additional support for returning a value. Someone might replace all launch functions with async, and the code would work fine. Don't do that! async is about producing value. If we don't need to, we should prefer using launch.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { // Don't do that! // possible, but a bad practice GlobalScope.async { delay(1000L) println("World!") } println("Hello,") delay(2000L) } // Hello, // (1 sec) // World! //sampleEnd

async builder is often used to parallelize two processes, like obtaining data from two different places, to combine them together.

scope.launch { val news = async { newsRepo.getNews() .sortedByDescending { it.date } } val newsSummary = async { newsRepo.getNewsSummary() } view.showNews( newsSummary.await(), news.await() ) }

Structured Concurrency

If a coroutine is started on GlobalScope, the program will not wait for it. As previously mentioned, coroutines are not blocking any threads, and nothing is preventing the program from ending. This is why in the below example, additional delay on the end of the runBlocking needs to be called if we want to see any "World!" printed.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { GlobalScope.launch { delay(1000L) println("World!") } GlobalScope.launch { delay(2000L) println("World!") } println("Hello,") // delay(3000L) } // Hello, //sampleEnd

Everything changes if we get rid of the GlobalScope here. runBlocking creates a CoroutineScope and provides it as its lambda expression receiver. Thus we can call this.launch or simply launch. As a result, launch becomes a child of runBlocking. As parents might recognize, parental responsibility is to wait for all its children, so runBlocking will suspend until all its children are finished.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { launch { delay(1000L) println("World!") } launch { delay(2000L) println("World!") } println("Hello,") } // Hello, // (1 sec) // World! // (1 sec) // World! //sampleEnd

How does it work? Let's take a look at the signatures of runBlocking, launch, and async.

fun <T> runBlocking( context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T ): T fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job fun <T> CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred<T>

You might notice that the block parameter type has a receiver of type CoroutineScope. It means that in the lambda expression of a coroutine builder, the receiver (this) is of type CoroutineScope. At the same time, launch and async are extension functions of the same type. This is why we don't need to use GlobalScope inside runBlocking - just calling launch inside runBlocking means calling it on the receiver this.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { this.launch { // same as just launch delay(1000L) println("World!") } launch { // same as this.launch delay(1000L) println("World!") } println("Hello,") } // Hello, // (1 sec) // World! // World! //sampleEnd

A parent provides a scope for its children, and they are called on this scope. This builds a relationship that is called a structured concurrency. Here are the most important effects of the parent-child relationship:

  • children inherit context from their parent (but can also overwrite it, it will be explained in the chapter Coroutine context),
  • a parent suspends until all the children are finished (it will be explained in the chapter Job and children awaiting),
  • when the parent is canceled, its child coroutines are canceled too (it will be explained in the chapter Cancellation),
  • when a child raises an error, it destroys a parent as well (it will be explained in the chapter Exception handling).

Bigger picture

Suspending functions need to be called from other suspending functions. This all needs to start with some coroutine builder. Except for runBlocking, builders need to be started on CoroutineScope. In our simple examples, the scope is provided by runBlocking, but in bigger applications, it is either constructed by us (we will explain how to do it in the chapter Constructing coroutine scope) or provided by the framework we use (for instance Ktor on a backend or Android KTX on Android). Once the first builder is started on a scope, other builders can be started on the scope of the first builder, and so on. This is in essence how our applications are structured.

class NetworkUserRepository( private val api: UserApi, ) : UserRepository { suspend fun getUser(): User = api.getUser().toDomainUser() } class NetworkNewsRepository( private val api: NewsApi, private val settings: SettingsRepository, ) : NewsRepository { suspend fun getNews(): List<News> = api.getNews() .map { it.toDomainNews() } suspend fun getNewsSummary(): List<News> { val type = settings.getNewsSummaryType() return api.getNewsSummary(type) } } class MainPresenter( private val view: MainView, private val userRepo: UserRepository, private val newsRepo: NewsRepository ) : BasePresenter { fun onCreate() { scope.launch { val user = userRepo.getUser() view.showUserData(user) } scope.launch { val news = async { newsRepo.getNews() .sortedByDescending { it.date } } val newsSummary = async { newsRepo.getNewsSummary() } view.showNews(newsSummary.await(), news.await()) } } }

There is one problem though: What about suspending functions? We can suspend there, but we do not have any scope. Passing it as an argument is not a good solution (as we will see in the chapter Scoping functions). Instead, we should use coroutineScope function.

Using coroutineScope

Imagine, that in some repository function, you need to asynchronously load two resources, for example user data and a list of articles. Then you want to return only those articles that should be seen by the user. To call async, we need a scope, but we don't want to pass it to a function6. To create a scope out of a suspending function, we use the coroutineScope function.

suspend fun getArticlesForUser( userToken: String?, ): List<ArticleJson> = coroutineScope { val articles = async { articleRepository.getArticles() } val user = async { userService.getUser(userToken) } articles.await() .filter { canSeeOnList(user.await(), it) } .map { toArticleJson(it) } }

The second async does not need to be used - without it, articles and user would be requested concurrently anyway. I decided to keep it, to make this example more readable (to highlight that those two requests happen asynchronously).

coroutineScope is just a suspending function, creating a scope for its lambda expression. The function returns whatever is returned by the lambda expression (like let, run, use, or runBlocking). So in the above example, it returns List<ArticleJson>, because this is what is returned from the lambda expression.

coroutineScope is a standard function we use when we need a scope inside a suspending function. It is really important. The way it is designed is perfect for this use case, but to analyze it, we first need to learn a bit about context, canceling, and exception handling. This is why the function will be explained in detail later, in a dedicated chapter (Scoping functions).

We can also start using the suspending main function together with coroutineScope, which is a modern alternative to using the main function with runBlocking.

import kotlinx.coroutines.* //sampleStart suspend fun main(): Unit = coroutineScope { launch { delay(1000L) println("World!") } println("Hello,") } // Hello, // (1 sec) // World! //sampleEnd

Summary

This knowledge is enough for most Kotlin coroutines usages. In most cases, we just have suspending functions calling other suspending or normal functions. If we need to introduce concurrent processing, we wrap a function with a coroutineScope and use builders inside. Everything needs to start with some builders called on some scope. We will learn in later parts how to construct such a scope, but for most projects, it needs to be defined once and is touched rarely.

Even though we learned the essentials, there is still a lot to learn about. In the next chapters, we will dive deep into coroutines. We will learn to use different contexts, how to tame cancellations, exception handling, how to test coroutines, etc. There are still a lot of great features to discover.

1:

It can also start from suspending main, but even though we use it a lot in examples, it is not helpful when we write Android or backend applications.

2:

Daemon thread is a low-priority thread that runs in the background. Here I compare launch to the daemon thread because both do not stop the program from ending.

3:

To be more precise, it runs a new coroutine and blocks the current thread interruptibly until its completion.

4:

The reason is that runBlocking creates a scope while suspending main does not if we don’t use the coroutineScope function, which we will introduce later.

5:

In general, by the argument of a functional type. It might be a lambda expression, but also an anonymous function or function reference.

6:

In the chapter Scoping functions, we will explain in detail why.