suspend
and runBlocking
functionsThe arrival of the Kotlin programming language into the Android ecosystem ruffled a lot of feathers and forever changed the way Android development will be approached by both experienced and novice programmers. Between the new functional programming paradigms, and the plethora of new APIs to try, Kotlin will change the way we work with Android forever.
Most notable was the introduction of coroutines, which redefines the way we do concurrency, asynchronicity, and the approach to multi-threading. Coroutines, as Kotlin’s official documentation explains, give the programmer the ability to start light-weight threads that may suspend the execution of a block of code from any given thread.
This new approach allows programmers to create coroutines that run asynchronously away from the UI thread. They can block either the main thread or another thread, or they can run concurrently, in parallel. The coroutines can even be started in one thread, and returned in another.
Long story short, coroutines give developers the power to implement a multi-threading strategy that does not require the expensive transaction of spawning new threads, but instead juggles multiple coroutines that may be started from any thread and resumed from another, all while using a smaller amount of working threads.
There’s a lot more information we could explore with Kotlin’s coroutines, but in this article I will focus my attention on two main aspects of this pattern: the differences between the suspend
and runBlocking
keywords, and how both concepts are related to the coroutine pattern.
To jump ahead:
coroutineScope
coroutineScope
suspend
and runBlocking
functions relate?suspend
and runBlocking
functionsAlmost everything in Kotlin Coroutines revolves around the
suspend
function. — DelfStack
Let’s start by defining what exactly both of the suspend
and runBlocking
keywords mean.
The suspend
keyword is a function modifier, and it denotes that the given function be made into a suspending function. According to Kotlin’s official documentation, suspending functions can be used inside coroutines just like regular functions, but they have an additional feature that allows them to use other suspending functions inside of them:
suspend fun firstFunction() { delay(1000L) // delay is also a suspending function println("This is a suspend function") }
It’s worth noting that suspend
functions cannot be executed like any other functions. As DelftStack explains, suspending functions need to be invoked through either another suspend
function, or from a coroutine itself. That means that any given program will have to build a coroutineScope
in order to start executing suspending functions altogether:
suspend fun secondFunction() = coroutineScope { // this: CoroutineScope launch { // launch is a suspending function delay(500L) print("LogRocket!") } print("Hello") }
coroutineScope
Before we look into the runBlocking
function as promised above, we’ll first need to take a small detour and quickly define what a coroutineScope
is.
A coroutineScope
allows for any given thread to jump onto the coroutine world, and execute code concurrently with the rest of the program. As we suggested above, a coroutineScope
allows the launch and execution of further coroutines or suspend functions.
Additionally, and as Kotlin’s official documentation explains, when a coroutineScope
is created, it will not be complete until all launched children complete. The coroutineScope
function above is also a suspending function, just like any suspend
function.
Unfortunately, the extent and nature of the coroutineScope
is not the topic of this article. For more information regarding the coroutineScope
and its different usage, check out my fellow LogRocketeer’s article on the coroutine world.
coroutineScope
Returning to the topic at hand — the runBlocking
function is instead a couroutine builder, or as Kotlin’s website better explains, it bridges the non-coroutine world of a regular function and the code with coroutines inside the block’s curly braces:
fun main() = runBlocking { // this: CoroutineScope println("We're inside a blocking CoroutineScope") }
You’ll notice that this last definition may sound similar to the one given for coroutineScope
. That is because the runBlocking
function creates a coroutineScope
inside of it, just as the suspending function coroutineScope
did above. Similarly, they both wait for all of their children to complete before they complete themselves.
The main difference between a regular coroutineScope
and the runBlocking
function is twofold. First, while the coroutineScope
is a suspending function, runBlocking
is not, and instead it is only a regular function. This difference is due to the fact that runBlocking
blocks the calling thread from further execution until it completes, while the alternative simply suspends the execution into a working thread:
fun main() { runBlocking { // this: CoroutineScope launch { delay(500L) println("the") } println("Follow") } println("execution.") }
As demonstrated in the example above, what the runBlocking
function is achieving is that it is creating a coroutineScope
from which to execute further coroutines, all while blocking the calling thread. From there, it launches another suspend
function that delays itself while the initial scope continues execution and prints the initial string "Follow"
. Once the delay in the second coroutine is completed, it will print the second string "the"
, followed by the completion of the blocking scope, and finalize the program by printing the last string "execution"
.
suspend
and runBlocking
functions relate?At this point, we’ve gone through a lot of information. We learned that suspend
functions are not much more than regular functions, other than that they require to be called by a coroutine, or another suspend
function on its own. We also learned what a coroutineScope
is, and dove deep into one of the examples of a coroutineScope
that blocks the calling thread from further execution.
But how does it all relate? Let’s put both of these main ideas together into the same program to further explore their relationship:
fun main() { runBlocking { // this: CoroutineScope suspendingWork() println("the") } println("execution.") } suspend fun suspendingWork() { delay(1000L) println("Follow") }
**Output:** > Follow > the > execution.
In the example above, you’ll notice that we use all of the ideas we explored before in the same code snippet. The function main()
consists of a runBlocking
scope that blocks the thread until it finishes its execution, followed by a simple print statement.
Following the flow of execution, we start off by getting into the runBlocking()
scope block, while blocking the thread from further execution. From there, a suspending function is invoked with suspendingWork()
, which the blocking scope follows sequentially until it finishes execution with the program’s first print statement that reads "Follow"
. As soon as the program returns back to the blocking coroutineScope
block, it prints the second statement reading "the"
. Lastly, the blocking coroutineScope
releases the thread, and finally allows the last print statement reading "execution"
at the very end of the program.
That was a mouthful, but hopefully everyone was able to follow. At the very least, the flow of execution should be noted by following the sentence that is trying to form in plain English.
In the next section, I’ll provide a few more examples, along with their outputs, to further demonstrate how suspend
functions and codes that are run inside the runBlocking
scope differ, with respect to the flow of execution of their print statements.
suspend
and runBlocking
functionsWe ended the previous section by fleshing out an example that demonstrated the relation of a suspend
function with respect to the runBlocking
function, and how they would behave around each other. In this section, we’ll explore a few more examples to emphasize further attributes and the behavior of both concepts, and better understand how and when to use them.
For this first example, we’ll start with the example we ended the last section with, but with a slight modification that showcases the call to the suspendingWork()
function being suspended inside a launch
suspend function.
It should be noted that this small difference greatly changes the order of operations, as the block of code inside suspendingWork()
is, well, suspended into its own coroutine, and delays the given coroutine for a second before it continues execution. By the time that happens, the initial print statement inside the blocking scope has already been executed, as the suspended piece of work allowed that scope to continue execution. Finally, the print statement outside of the blocking scope is executed, like the example from before:
fun main() { runBlocking { // this: CoroutineScope launch { suspendingWork() } println("Follow") } println("execution.") } suspend fun suspendingWork() { delay(1000L) println("the") }
**Output:** > Follow > the > execution.
For this second example, we explore the idea of launching two different instances of the same suspendingWork()
function simultaneously from the same blocking scope, followed by a simple print statement that reads "Hello"
.
The output is very obvious in this example, but it’s worth noting that whenever two different suspend
functions are launched simultaneously like this, most of the time they will return in the same order as they were created. A simple modification to this example, where we number the calls to the single suspend
function, would showcase this in a compiler:
fun main() = runBlocking { // this: CoroutineScope launch { suspendingWork() } launch { suspendingWork() } println("Hello") } suspend fun suspendingWork() { delay(1000L) println("LogRocket!") }
Output: > Hello > LogRocket! > LogRocket!
This example is a bit more involved. We’re back at following the execution, where our new suspend
function simply prints the parameter that it receives. This time, we suspend two different versions of the function side to their own coroutines, while calling the same function inside the blocking scope with our first print statements. The delay
calls should give away which suspended piece of work prints first, and as usual, the print statement outside of the blocking scope is printed at the very end:
fun main() { runBlocking { // this: CoroutineScope launch { delay(500L) supendedPrint("printing") } launch { supendedPrint("the") } supendedPrint("Follow") } println("execution.") } suspend fun supendedPrint(value: String) { println(value) }
Output: > Follow > the > printing > execution.
For this example, let’s circle back to an old friend. The suspend
function is now composed of a coroutineScope
of its own, meaning that it can spawn further coroutines inside of it at will, like the runBlocking
function.
The purpose of this example is to emphasize that neither the regular coroutineScope
, nor the blocking one will finish execution until all of its launched children complete themselves.
Because the initial runBlocking
function launches the suspend
function suspendingWork()
, which creates a coroutineScope
of its own, the print statements outside of the runBlocking
call will wait until everything else in this program finalizes its execution before it takes its turn doing so:
fun main() { runBlocking { // this: CoroutineScope suspendingWork() println("of") } println("execution.") } suspend fun suspendingWork() = coroutineScope { // this: CoroutineScope launch { delay(2000L) println("flow") } launch { delay(1000L) println("the") } println("Follow") }
Output: > Follow > the > flow > of > execution.
In this final example, we’re putting it all together. We start off by blocking the thread calling main()
, and printing our first string inside of it. Diving into the first suspend
function of suspendingWork()
, we create another coroutineScope
where we suspend two further coroutines and print our second string.
From here, the flow of execution is more or less the same as the example above, before coming back to the blocking scope that’s been waiting on the internal coroutineScope
before continuing execution. Here we print the second to last string before getting out of the blocking scope and printing our last statement.
It should be noted that the second suspend
function is able to be called from anywhere in the program, except inside main()
,whenever we’re not inside the blocking coroutineScope
:
fun main() { runBlocking { // this: CoroutineScope suspendedPrint("Try") suspendingWork() suspendedPrint("of") } println("execution.") } suspend fun suspendingWork() { coroutineScope { // this: CoroutineScope launch { delay(2000L) suspendedPrint("the") } launch { delay(1000L) suspendedPrint("follow") } suspendedPrint("to") } suspendedPrint("flow") } suspend fun suspendedPrint(value: String) { println(value) }
Output: > Try > to > follow > the > flow > of > execution.
We have gone through a lot of material here, and plenty of examples. We should now know the difference between suspend
and runBlocking
functions, and how they relate to each other.
Additionally, we learned what a coroutineScope
is and how it is used, as well as its relationship to the runBlocking
scope, which is a coroutineScope
itself, and how only these kinds of scopes, as well as other suspending functions, are allowed to be called suspend
functions.
There are a lot more examples that can be derived from the ones we’ve gone over. If interested, I recommend playing around with the new Kotlin Playground tool to quickly compile and run simple Kotlin programs using what we learned!
LogRocket is an Android monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your Android apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your Android apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to implement one-way and two-way data binding in Vue.js, using v-model and advanced techniques like defineModel for better apps.
Compare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.