How does suspension work in Kotlin coroutines?

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

Suspending functions are the hallmark of Kotlin coroutines. The suspension capability is the single most essential feature, upon which all other concepts are built. That is why in this chapter our goal is to forge a solid understanding of how it works.

Suspending a coroutine means stopping it in the middle. It is similar to stopping a video game - you save at a checkpoint, turn off the game, and both you and your computer can concentrate on doing different things. Then, sometime later, you would like to continue, so you turn on the game again, resume from the saved checkpoint, and thus you can play from where you previously left off.

It is an analogy to coroutines. They also can be stopped, and when they are stopped, they return a Continuation. It is like a save in a game - we can use it to continue from the point where we've stopped.

Resume

So let's see it in action. For that, we need a coroutine. The easiest way to create one is by starting a suspending main function. This will be our starting point.

import kotlin.* //sampleStart suspend fun main() { println("Before") println("After") } // Before // After //sampleEnd

This is a simple program that will print "Before" and "After". What is going to happen if we suspend in between? For that, we can use the suspendCoroutine function provided by the Kotlin standard library1.

import kotlin.coroutines.* //sampleStart suspend fun main() { println("Before") suspendCoroutine<Unit> { } println("After") } // Before //sampleEnd

If you call the above code, you will not see the "After", and the code will not stop running (as our main function never finished). The coroutine is suspended after "Before". Our game was stopped and never resumed. So how can we do that? Where is this mentioned Continuation, that can be used to resume?

Take a look again at the suspendCoroutine invocation, and notice that it ends with a lambda expression ({ }). The function passed as an argument will be invoked before the suspension. This function gets a continuation as an argument.

import kotlin.coroutines.* //sampleStart suspend fun main() { println("Before") suspendCoroutine<Unit> { continuation -> println("Before too") } println("After") } // Before // Before too //sampleEnd

Such a function calling another function in place is nothing new. This is similar to let, apply, or useLines. Function suspendCoroutine is designed this way, which makes it possible to use continuation just before the suspension. After the suspendCoroutine call, it would be too late. So the lambda expression passed as a parameter to the suspendCoroutine function is invoked just before the suspension. This lambda is used to store this continuation somewhere or to plan whether to resume it.

We could use it to resume immediately:

import kotlin.coroutines.* //sampleStart suspend fun main() { println("Before") suspendCoroutine<Unit> { continuation -> continuation.resume(Unit) } println("After") } // Before // After //sampleEnd

Notice that “After” in the example above is printed because we call resume in suspendCoroutine.

Since Kotlin 1.3, the definition of Continuation has been changed. Instead of resume and resumeWithException, there is one resumeWith function expecting Result. The resume and resumeWithException, which we are using, are extension functions from the standard library using `resumeWith. They do not need to be imported.

inline fun <T> Continuation<T>.resume(value: T): Unit = resumeWith(Result.success(value)) inline fun <T> Continuation<T>.resumeWithException( exception: Throwable ): Unit = resumeWith(Result.failure(exception))

We could also use it to start a different thread, that will sleep for a set duration and resume after that time:

import kotlin.concurrent.thread import kotlin.coroutines.* //sampleStart suspend fun main() { println("Before") suspendCoroutine<Unit> { continuation -> thread { Thread.sleep(1000) continuation.resume(Unit) } } println("After") } // Before // (1 second delay) // After //sampleEnd

This is an important observation. Notice that starting a thread can be extracted into a function, and a resume might happen in a callback. In such a case, the continuation is captured by the lambda expression as shown in the code snippet below.

import kotlin.concurrent.thread import kotlin.coroutines.* //sampleStart fun invokeAfterSecond(operation: () -> Unit) { thread { Thread.sleep(1000) operation.invoke() } } suspend fun main() { println("Before") suspendCoroutine<Unit> { continuation -> invokeAfterSecond { continuation.resume(Unit) } } println("After") } // Before // (1 second delay) // After //sampleEnd

Such a mechanism works, but it unnecessarily creates threads to end them just after a second of inactivity. Threads are not cheap, so why waste them? A better way would be to set up an alarm clock. On JVM, we can use ScheduledExecutorService for that. We can set it to call some continuation.resume(Unit) after a set amount of time.

import java.util.concurrent.* import kotlin.coroutines.* //sampleStart private val executor = Executors.newSingleThreadScheduledExecutor { Thread(it, "scheduler").apply { isDaemon = true } } suspend fun main() { println("Before") suspendCoroutine<Unit> { continuation -> executor.schedule({ continuation.resume(Unit) }, 1000, TimeUnit.MILLISECONDS) } println("After") } // Before // (1 second delay) // After //sampleEnd

Suspending for a set amount of time seems like a useful feature. Let's extract it into a function. We will name it delay.

import java.util.concurrent.* import kotlin.coroutines.* //sampleStart private val executor = Executors.newSingleThreadScheduledExecutor { Thread(it, "scheduler").apply { isDaemon = true } } suspend fun delay(time: Long): Unit = suspendCoroutine { cont -> executor.schedule({ cont.resume(Unit) }, time, TimeUnit.MILLISECONDS) } suspend fun main() { println("Before") delay(1000) println("After") } // Before // (1 second delay) // After //sampleEnd

This is exactly how delay from the Kotlin Coroutines library used to be implemented. The current implementation is more complicated, mainly to support testing, but the essential idea stays the same.

Resuming with a value

One thing that might concern you is why did we pass the Unit to the resume function. You might also be wondering why I used Unit as a type argument to the suspendCoroutine. The fact that those two are of the same type is no coincidence. It is also returned from the function and the generic type of Continuation parameter.

val ret: Unit = suspendCoroutine<Unit> { cont: Continuation<Unit> -> cont.resume(Unit) }

When we call suspendCoroutine, we can specify which type will be returned in its continuation. The same type needs to be used when we call resume.

import kotlin.coroutines.* //sampleStart suspend fun main() { val i: Int = suspendCoroutine<Int> { cont -> cont.resume(42) } println(i) // 42 val str: String = suspendCoroutine<String> { cont -> cont.resume("Some text") } println(str) // Some text val b: Boolean = suspendCoroutine<Boolean> { cont -> cont.resume(true) } println(b) // true } //sampleEnd

This does not fit well with the game analogy. I don't know any game, where while resuming a save you can put something inside the game (unless it is new knowledge if you cheated and googled how to solve the next challenge). Although it makes perfect sense with coroutines. Often we are suspended because we wait for some data. For instance, when we need to get a network response from the API. This is a common scenario. Your thread is running business logic until it reaches a point where it needs some data. So it asks your network library to deliver it. Without coroutines, this thread would then need to sit and wait. This would be a huge waste - threads are expensive. Especially if this is an important thread, like the Main Thread on Android. With coroutines, it just suspends, and gives the library a continuation with the instruction "Once you got those data, just put them to the resume function". Then the thread can go do other things. Once the data are there, this or some other function (depending on the dispatcher we set) will be grabbed and used to resume from the point where the coroutine was suspended.

To see it in action, let's simulate our network library with a callback function.

import kotlin.concurrent.thread import kotlin.coroutines.* //sampleStart data class User(val name: String) fun requestUser(callback: (User) -> Unit) { thread { Thread.sleep(1000) callback.invoke(User("Test")) } } suspend fun main() { println("Before") val user = suspendCoroutine<User> { cont -> requestUser { user -> cont.resume(user) } } println(user) println("After") } // Before // (1 second delay) // User(name=Test) // After //sampleEnd

Calling suspendCoroutine directly is not convenient. We would prefer to have a suspending function instead. We can extract it ourselves.

suspend fun requestUser(): User { return suspendCoroutine<User> { cont -> requestUser { user -> cont.resume(user) } } } suspend fun main() { println("Before") val user = requestUser() println(user) println("After") }

Currently, you rarely need to wrap a callback function to make it a suspending one, because suspending functions are already supported by several popular libraries such as Retrofit, Room, and many others. Now you have an idea of what those functions do under the hood. It is similar to what we just presented. Except they rather use the suspendCancellableCoroutine function, which supports cancellation. Both will be described in detail in the chapter ​​Cancellation.

suspend fun requestUser(): User { return suspendCancellableCoroutine<User> { cont -> requestUser { user -> cont.resume(user) } } }

You might wonder what if the API does not give us data but some troubles instead. What if the service is dead or responds with an error. In such a case, we cannot return data, but instead, we should throw an exception from the place where the coroutine was suspended. This is where we need to resume with an exception.

Resume with exception

Every function we call might return some value or throw an exception. The same is true for suspendCoroutine. When resume is called, it returns the data passed as an argument. When resumeWithException is called, the exception passed as an argument is conceptually thrown from the suspension point.

import kotlin.coroutines.* //sampleStart class MyException : Throwable("Just an exception") suspend fun main() { try { suspendCoroutine<Unit> { cont -> cont.resumeWithException(MyException()) } } catch (e: MyException) { println("Caught!") } } // Caught! //sampleEnd

This mechanism is used in case of different kinds of problems. For instance, to signalize network exceptions.

suspend fun requestUser(): User { return suspendCancellableCoroutine<User> { cont -> requestUser { resp -> if (resp.isSuccessful) { cont.resume(resp.data) } else { val e = ApiException( resp.code, resp.message ) cont.resumeWithException(e) } } } }

Suspending a coroutine, not a function

One thing that needs to be stressed here is that we suspend a coroutine, not a function. Imagine that we store a function in some variable and try to resume it after the function call.

import kotlin.coroutines.* //sampleStart // Do not do this var continuation: Continuation<Unit>? = null suspend fun suspendAndSetContinuation() { suspendCoroutine<Unit> { cont -> continuation = cont } } suspend fun main() { println("Before") suspendAndSetContinuation() continuation?.resume(Unit) println("After") } // Before //sampleEnd

This makes no sense. It is equivalent to stopping a game, and planning to resume it during later parts of that game. resume will never be called. You will only see "Before", and your program will never end. Resuming needs to happen in another thread or another coroutine. To show it, we can set a thread to resume a coroutine after a second.

import kotlinx.coroutines.* import kotlin.coroutines.* //sampleStart // Do not do this, potential memory leak var continuation: Continuation<Unit>? = null suspend fun suspendAndSetContinuation() { suspendCoroutine<Unit> { cont -> continuation = cont } } suspend fun main() = coroutineScope { println("Before") launch { delay(1000) continuation?.resume(Unit) } suspendAndSetContinuation() println("After") } // Before // (1 second delay) // After //sampleEnd

Summary

I hope now you have a clear picture of how suspension works from the user’s point of view. It is important, and we will see it throughout the book. It is also practical, as now you can take callback functions and make them suspending functions. If you are like me and like to know exactly how things work, you are likely still wondering about how it is implemented. If you're curious about it, this will be covered in the next chapter. If you don't feel you need to know, just skip it. It is not very practical, it just reveals the magic of Kotlin coroutines.

1:

It directly calls suspendCoroutineUninterceptedOrReturn that is a primitive function, that is a function without visible implementation because it is intrinsic.