Blocking threads, suspending coroutines

Traffic congestion image from wikipedia, CC BY-SA 3.0

Blocking threads

How can a thread be blocked? There are two different ways to block a thread. One is to run a CPU-intensive computation that takes a lot of time (aka CPU-bound task). For example, the following (non-secure) function that generates 4096-bit prime number takes around 10 seconds to execute on my machine

fun findBigPrime(): BigInteger = 
BigInteger.probablePrime(4096, Random())
fun BufferedReader.readMessage(): Message? =
readLine()?.parseMessage()

Suspending coroutines

Coroutines provide an alternative to thread blocking by supporting suspension. So, what is the difference between blocking a thread and suspending a coroutine? Let us take a look at the following snippet of sequential code:

val data = awaitData() // does it block or suspend?
processData(data)

Recognizing blocking code

Java APIs on JVM platform are usually explicit about their blocking behavior in their documentation. In the core APIs the right term is usually used, making blocking behavior easy to recognize. For example, when you examine the documentation for InputStream.read method you see the following:

Suspending functions

The Kotlin programming language introduces the concept of suspending functions via the suspend modifier. One mistake that is often made is that adding a suspend modifier to a function makes it either asynchronous or non-blocking. You can even notice this mistake in the talk “Exploring Coroutines in Kotlin” by Venkat Subramariam from KotlinConf 2018. Let us examine this mistake closer by adding suspend modifier to our first example of blocking function:

suspend fun findBigPrime(): BigInteger = 
BigInteger.probablePrime(4096, Random())

Suspending convention

Suspending functions add a new dimension to code design. It was blocking/non-blocking without coroutines and now there is also suspending/non-suspending on top of that. To make everybody’s life simpler we use the following convention: suspending functions do not block the caller thread.

suspend fun findBigPrime(): BigInteger =
withContext(Dispatchers.Default) {
BigInteger.probablePrime(4096, Random())
}

Blocking IO to suspending

Now, let us take a look at the second example of a blocking function — an IO-bound one. We turn it into a suspending function in a similar way:

suspend fun BufferedReader.readMessage(): Message? =
withContext(Dispatchers.IO) {
readLine()?.parseMessage()
}

Conclusion

Using withContext is not the only way to get you suspending functions that do not block. Another way is to use truly asynchronous (non-blocking) library functions to start with. For example, instead of turning a blocking Thread.sleep method into a suspending function via withContext, you should simply use a suspending delay function.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store