Learning Kotlin Coroutines as a Java Dev (Part I)

Patson Luk
4 min readMay 10, 2019

As a part of my adventure to experiment with JVM languages, Kotlin came out on the top of my list because it’s concise and yet very readable for a Java developer like me.

One thing that didn’t come naturally to me is the concept of coroutines and suspending functions, especially when I attempted to understand them from a traditional Java threading perspective.

First, let’s take a quick look at a simple example of suspending function in coroutines:

The first thing you’ll notice is there is nothing special about this suspending function, except myMethod is marked with the keyword suspend.

In fact, all the rest of the code looks sequential, and the println statement would only get executed after longComputation returns. So how exactly do coroutines and suspending functions enable asynchronous (i.e., non-blocking) code execution?

Let me share what misconceptions I had to overcome and what I discovered about the advantages of coroutines as compared with Java threads.

Misconception #1

“Using coroutines and suspending functions are the only option for asynchronous operation for Kotlin.”

Coroutines and suspending functions provide a safer and more readable option for asynchronous operation. All of the existing Java concurrency libraries are still available. I had trouble making progress before I understood this, because coroutines often make use of existing Java concurrency facilities.

Misconception #2

“Invoking a function marked as ‘suspend’ will start that function asynchronously and the current thread will continue to execute the next statement.”

Unfortunately, nothing gets printed until waiting for the full 10-second delay, so this assumption was incorrect.

Invoking a function marked as suspend alone does not launch any new task/threads. A function marked as suspend only indicates that the function might take a while to finish and there might be suspension points (I’ll get to these shortly) within its body. In fact, calling a suspending function is no different than calling a regular function in terms of code execution order; all the statements that follow the suspending function invocation will only be executed after the suspending function returns.

The trick to achieving non-blocking code execution is building coroutines that invoke suspending functions. For example using the `launch` function:

launch { //starts a new coroutine

}

Misconception #3

“OK, so I will launch two coroutines side by side and invoke my ‘suspend’ functions from within. In the suspend function bodies, I can make any long running blocking function calls, and they should run asynchronously (i.e., not blocking other coroutines)!”

The above code will print the line to console after five seconds, then block for another five seconds before printing the line again. It is still blocking!

If none of the code inside the suspending function body gives a valid suspension point, then the code will just run sequentially all the way without any chance to suspend.

Suspension points are places where code execution can be “suspended” so that the current thread can switch out to work on another coroutine. A suspension point can be introduced by calling another suspending function such as delay.

Misconception #4

“Hey, if I explicitly provide Dispatchers.Default to the launch builder, then it seems to be working — both the myFailedSuspendAttempt calls appear to be executed concurrently as the two println statements are executed after five seconds.”

This has nothing to do with myFailedSuspendAttempt being declared as suspend. In fact, the suspend keyword is marked as redundant by the IDE and can be removed as there is no valid suspension point within myFailedSuspendAttempt.

The code is executed concurrently simply because the child coroutines are dispatched using Dispatchers.Default. Dispatcher executes the corresponding coroutines with its own threading strategy. Dispatcher.Default uses a worker thread pool, hence the two myFailedSuspendAttempt calls are handled by different worker threads without blocking each other.

Misconception #5

“Statements inside the suspending method body should all be executed by the same thread and this thread should never switch out of this method body in the middle of execution”

This misconception can be demonstrated by two pieces of code:

Java code

Kotlin code

In Java, it is guaranteed that for myMethod:

  1. The thread that prints checkpoint 1 would be the same thread as checkpoint 2.
  2. The thread will stay within myMethod until the whole method body is executed.

However in our Kotlin mySuspendFunction:

  1. The thread that prints checkpoint 1 and checkpoint 2 could be different.
  2. The thread that executes checkpoint 1 can switch out to execute code from other coroutine after hitting the suspension point (delay).

Conclusions

Coroutines build around the concept of suspending and resuming around suspension points.

Suspension points split code into individually executable pieces of code (Code Blocks). While the pieces are run sequentially they can be executed by any thread that is available when they become runnable. It’s analogous to an operating system running jobs — any one of the CPU cores can execute a process when the process becomes runnable.

When a coroutine is suspended:

  1. The current code within this coroutine will not execute any further until it’s resumed (by some conditions, such as the corresponding asynchronous operation finishes, or in the case of delay function, has waited for the specified delay time).
  2. The thread that runs the current coroutine could execute a different coroutine’s Code Block that is ready to be processed.

The main advantages I discovered while using coroutines are:

  1. It is easier to reason about code written using coroutines and suspending functions as code statements within them are executed sequentially.
  2. They are lighter weight than Java threads — they achieve concurrency with optimized usage of threads. The dispatcher thread is free to run other coroutine code blocks when the current coroutine is suspended.

In our next blog, we are going to dive deeper into the coroutine compilation process and code execution to understand how Kotlin implements coroutine’s asynchronous behavior under the hood!

About the Author

Patson Luk is a developer with experience in a variety of domains, from large-scale enterprise banking system to lightweight mobile payment solutions. He now leads Java development for SolarWinds ® AppOptics ™ , a web application performance monitoring product. Patson’s focus is on using Java bytecode manipulation technologies to gain greater visibility into the full spectrum of Java-based applications.

--

--