Basic concepts about Kotlin Coroutine – Coroutine and Suspend functions

coroutine-suspend-function

Hello guys! This is the first article in a series of tutorials on Kotlin coroutine. The articles will go from basic to advanced levels. I hope it will help you guys.

What is a coroutine?

You can imagine Corotine as “lightweight threads”. We can have multiple coroutines running on one thread (the regular thread we know in Java and Kotlin). Here, each coroutine (lightweight thread) can be suspended without causing the thread it runs on to block.
To create a coroutine, you will need something called CoroutineScope. It defines the environment that coroutines will use. When the scope no longer exists, the coroutines inside it will also be automatically destroyed.

We have an example of a coroutine.

GlobalScope.launch {
  Log.d("Coroutine", "My first coroutine")
}

Here we have used GlobalScope (a provided form of CoroutineScope) and its launch method to create a coroutine. This coroutine performs the task of printing a log line. Simple, right?

We also have another way to start a coroutine.

val deferred = GlobalScope.async {
   Log.d("Coroutine","Start coroutine with Async")
   delay(500)
   5
}
GlobalScope.launch {
   val result = deferred.await()
   Log.d("Coroutine","Result = $result")
}

As you can see, this time we still use GlobalScope, but with async methods. Basically, async creates a Deferred object and returns the result in the future after completing its work. To get the result of 1 Deferred, we use the await method, as you saw in the code above. If you run this code, you will see bellow logs:

Start coroutine with Async

//After 500ms

Result = 5

To start a coroutine, we need a coroutine scope.
There are two ways to start a coroutine, which are launch and async.

In the example above, you see we are using the delay (500) ms method. This is a suspend function. If you try calling it outside of a coroutine, you will see the following error from the IDE:

The reason is quite obvious, you can only call 1 suspend function in 1 coroutine or in another suspend function. So let’s find out what the Suspend function is.

What is Suspend function?

It is a function marked with the suspend keyword. When a coroutine calls a suspend function, the coroutine will be suspended until that suspend function completes. Once the suspend function is completed, the next statements in the coroutine will be executed.
Let’s look at the following example

private suspend fun printDelayed(message: String, delay: Long) {
    delay(delay)
    Log.d("Coroutine",message)
}

GlobalScope.launch {
    printDelayed("First message", 100)
    printDelayed("Second message", 50)
}

 

Here we define suspend function printDelayed, this function simply delays a period of time and prints the message we pass in.
Then we start a coroutine and see the results. The temporal order of the log lines will be as follows

//After 100ms
First message
//After 150ms
Second message

You may wonder why for the second statement, we only delay 50ms but the corresponding log line appears last. The reason is because our coroutine has been “suspended” by the previous command. And only when the first statement is completed, will the second statement be executed. Quite easy to understand and obvious, right?
Let’s look at the following example:

GlobalScope.launch {
    //Parent coroutine
    launch {
    //Sub coroutine 1
        printDelayed("First message", 100)
    }
    launch {
    //Sub coroutine 2
        printDelayed("Second message", 50)
    }
}

When running this code, the log line order will change

//After 50ms
Second message
//After 100ms
First message

Why is that?
Here you see that GlobalScope created a coroutine (we call it Parent coroutine), then, in this Parent coroutine, we create 2 independent coroutines: Sub-coroutine 1 and Sub-coroutine 2. Calls to the function suspend only has a suspend effect on the coroutine that directly calls it. The parent coroutine is completely unaffected by the two calls to the printDelayed function. Its task is just to start 2 sub coroutines. And obviously, no suspension. So the 2 sub coroutines run independently and produce the log order as you see above.

The suspend function will suspend the coroutine that directly calls it, the next statements in that coroutine will only be called after the suspend function is completed.

Dig a little deeper into suspend function.

For example, we have a suspend function as follows:

suspend fun myFunction(param: Int): String {
    //Simulate long running task
    delay(1000)
    return "Result"
}

Under the hood, when compiled into byte code, it will look like this:

public final Object myFunction(int arg, @NotNull Continuation<? super String> $completion) {

    // body turned into a state machine, hidden for brevity

}

Here we can see that the function is added to a param of type Continuation. It acts as a callback that returns the results obtained after the calculation process. We can check the source code of Continuation

@SinceKotlin("1.3")
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext

/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}

It’s exactly like the callbacks we usually use, right? Therefore, suspend function helps us write code that looks synchronous, when in fact it is asynchronous. That is the great effect of suspend function.

Functions marked with the suspend keyword are transformed at compile time to be made asynchronous under the hood (in bytecode), even though they appear synchronous in the source code.

Leave a Reply

Your email address will not be published. Required fields are marked *