Ivan Garza Ivan is an Android engineer at Mixhalo, a passionate fútbol fan, and an amateur salsa maker.

Comparing the Kotlin suspend and runBlocking functions

7 min read 2080

Comparing The Kotlin Suspend And RunBlocking Functions

The 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:

Almost everything in Kotlin Coroutines revolves around the suspend function. — DelfStack

Kotlin suspending functions

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")
}

Kotlin’s 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.

Blocking 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".

How do the 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.

More examples of suspend and runBlocking functions

We 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.

Example 1

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.

Example 2

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!

Example 3

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.

Example 4

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.


More great articles from LogRocket:


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.

Example 5

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.

Conclusion

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: Instantly recreate issues in your Android apps.

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 — .

Ivan Garza Ivan is an Android engineer at Mixhalo, a passionate fútbol fan, and an amateur salsa maker.

Leave a Reply