article banner (priority)

Coroutine builders

This is a chapter from the book Kotlin Coroutines. You can find it 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 the normal to the suspending world1.

We are going to explore the three essential coroutine builders provided by the kotlinx.coroutines library:

  • launch
  • runBlocking
  • async

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

launch builder

The way launch works is conceptually similar to starting a new thread (thread function). We just start a coroutine, and it will run independently, like a firework that is launched into the air. This is how we use launch - to start a 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 CoroutineScope interface. This is part of an important mechanism called structured concurrency, whose purpose is to build a relationship between the parent coroutine and a child coroutine. Later in this chapter, we will learn about structured concurrency, but for now we will avoid this topic by calling launch (and later async) on the GlobalScope object. This is not a standard practice though as we should rarely use GlobalScope in real-life projects.

Another thing you might have noticed is that at the end of the main function we need to call Thread.sleep. Without doing this, this function would end immediately after launching the coroutines, so they wouldn't have a chance to do their job. This is because delay does not block the thread: it suspends a coroutine. You might remember from the How does suspension work? chapter 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 nothing stops the program from finishing (later we will see that if we use structured concurrency, Thread.sleep is not needed).

To some degree, how launch works is similar to a daemon thread2 but much cheaper. This metaphor is useful initially but it becomes problematic later. Maintaining a blocked thread is always costly, while maintaining a suspended coroutine is almost free (as explained in the Coroutines under the hood chapter). They both start some independent processes and need something that will prevent the program ending before they are done (in the example below, this 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 in which blocking is necessary. Like in the main function, we need to block the thread, 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 Thread.sleep(1000L)7.

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,") } // (1 sec) // World! // (1 sec) // World! // (1 sec) // World! // Hello, //sampleEnd

There are actually a couple of specific use cases in which runBlocking is 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 runBlocking in our example to replace Thread.sleep(2000) with delay(2000). Later we will see that it 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 use its successor runTest instead, which makes coroutines operate in virtual time (a very useful feature for testing which we will describe in the Testing coroutines chapter). 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 a value. This value needs to be returned by the lambda expression5. The async function returns an object of type Deferred<T>, where T is the type of the produced value. Deferred has a suspending method await, which returns this value once it is ready. In the example below, the produced value is 42, and 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() // (1 sec) 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 all their results. The returned Deferred stores a value inside itself once it is produced, so once it is ready it will be immediately returned from await. However, 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

How the async builder works is very similar to launch, but it has additional support for returning a value. If all launch functions were replaced with async, the code would still work fine. But don't do that! async is about producing a value, so if we don't need a value, we should use launch.

import kotlinx.coroutines.* //sampleStart fun main() = runBlocking { // Don't do that! // this is misleading to use async as launch GlobalScope.async { delay(1000L) println("World!") } println("Hello,") delay(2000L) } // Hello, // (1 sec) // World! //sampleEnd

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

scope.launch { val news = async { newsRepo.getNews() .sortedByDescending { it.date } } val newsSummary = newsRepo.getNewsSummary() // we could wrap it with async as well, // but it would be redundant view.showNews( newsSummary, news.await() ) }

Structured Concurrency

If a coroutine is started on GlobalScope, the program will not wait for it. As previously mentioned, coroutines do not block any threads, and nothing prevents the program from ending. This is why, in the below example, an additional delay at the end of runBlocking needs to be called if we want to see "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

Why do we need this GlobalScope in the first place? It is because launch and async are extension functions on the CoroutineScope. However, if you take a look at the definitions of these and of runBlocking, you will see that the block parameter is a function type whose receiver type is also CoroutineScope.

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>

This means that we can get rid of the GlobalScope; instead, launch can be called on the receiver provided by runBlocking, so with this.launch or simply launch. As a result, launch becomes a child of runBlocking. As parents might recognize, a parental responsibility is to wait for all their children, so runBlocking will suspend until all its children are finished.

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

A parent provides a scope for its children, and they are called in 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 they can also overwrite it, as will be explained in the Coroutine context chapter);
  • a parent suspends until all the children are finished (this will be explained in the Job and children awaiting chapter);
  • when the parent is cancelled, its child coroutines are cancelled too (this will be explained in the Cancellation chapter);
  • when a child raises an error, it destroys the parent as well (this will be explained in the Exception handling chapter).

Notice that, unlike other coroutine builders, runBlocking is not an extension function on CoroutineScope. This means that it cannot be a child: it can only be used as a root coroutine (the parent of all the children in a hierarchy). This means that runBlocking will be used in different cases than other coroutines. As we mentioned before, this is very different from other builders.

The bigger picture

Suspending functions need to be called from other suspending functions. This all needs to start with a 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 this in the Constructing coroutine scope chapter) or it is 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.

Here are a few examples of how coroutines are used in real-life projects. The first two are typical for both backend and Android. MainPresenter represents a case typical for Android. UserController represents a case typical for backend applications.

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()) } } } @Controller class UserController( private val tokenService: TokenService, private val userService: UserService, ) { @GetMapping("/me") suspend fun findUser( @PathVariable userId: String, @RequestHeader("Authorization") authorization: String ): UserJson { val userId = tokenService.readUserId(authorization) val user = userService.findUserById(userId) return user.toJson() } }

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

Using coroutineScope

Imagine that in some repository function you need to asynchronously load two resources, for example user data and a list of articles. In this case, 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 = userService.getUser(userToken) articles.await() .filter { canSeeOnList(user, it) } .map { toArticleJson(it) } }

coroutineScope is just a suspending function that creates 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, cancelling, and exception handling. This is why the function will be explained in detail later in a dedicated chapter (Coroutine scope 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

A diagram showing how different kinds of elements of the kotlinx.coroutines library are used. We generally start with a scope or runBlocking. In these, we can call other builders or suspending functions. We cannot run builders on suspending functions, so we use coroutine scope functions (like coroutineScope).

Summary

This knowledge is enough for most Kotlin coroutine 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 on its scope. 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 only rarely.

Even though we have learned the essentials, there is still a lot to learn about. In the next chapters, we will dive deeper 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 a suspending main, but even though we use it in a lot in examples, it is not helpful when we write Android or backend applications. It is also good to know that a suspending main is also just started by a builder, but we don't see it because Kotlin does that for us.

2:

A daemon thread is a low-priority thread that runs in the background. Here I compare launch to a 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 unless we use the coroutineScope function, which we will introduce later.

5:

To be strict with wording: by the argument of a functional type placed on the last position.

6:

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

7:

Using a dispatcher, we can make runBlocking run on a different thread. But still, the thread on which this builder has been started will be blocked until the coroutine is done.