Understanding Kotlin Coroutines: Fundamentals, Suspension, and Resumption
This article explains the core concepts of Kotlin coroutines, including what they are, how they differ from threads, the mechanics of suspension and resumption, the role of the suspend keyword, and practical code examples that illustrate their usage in Android development.
Preface
The author has used coroutines for a long time and wants to consolidate the understanding of Kotlin coroutines, focusing on the basic working principles rather than just usage, answering questions such as “What is a coroutine? What are suspend and resume? Is a coroutine more efficient than a thread?”
What Is a Coroutine?
Various sources describe a coroutine as a lightweight thread, a micro‑thread, or a user‑space thread that is invisible to the kernel. It is a cooperative code construct that differs across platforms (e.g., Go vs. Kotlin), so the article limits the discussion to Kotlin coroutines.
Why Call It a Coroutine? What Is Cooperation?
Threads in the JVM are pre‑emptive and scheduled by the OS; their execution order cannot be manually forced. The article shows a simple thread example with different priorities, demonstrating that thread scheduling is nondeterministic.
// Define a thread with priority 50 and start it
thread(priority = 50) {
Log.i(TAG,"start thread 1")
}
// Define a thread with priority 100 and start it
thread(priority = 100) {
Log.i(TAG,"start thread 2")
}It then shows a nested‑thread example to illustrate that ordering can be controlled, but this does not mean threads are cooperative.
Coroutines achieve cooperation by allowing the programmer to control execution order through suspension points.
import kotlinx.coroutines.*
fun main() = runBlocking {
// Launch first coroutine
val job1 = launch(Dispatchers.Default) {
repeat(10) { i ->
println("Job1: I'm working step $i...")
delay(200L)
}
}
// Wait for the first coroutine to finish
job1.join()
// Launch second coroutine
val job2 = launch(Dispatchers.Default) {
repeat(10) { i ->
println("Job2: I'm working step $i...")
delay(300L)
}
}
// Wait for the second coroutine to finish
job2.join()
println("main: All jobs are completed.")
}The example demonstrates how coroutines can be orchestrated to run sequentially while still being non‑blocking.
Suspend and Resume
What Is Suspension?
In programming, “thread suspension” means pausing the current thread and handing control to another thread. The article shows a classic Java wait/notify example and an Android‑style pseudo‑suspension example.
public class ThreadExample {
public static void main(String[] args) {
final Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 1: Holding lock...");
System.out.println("Thread 1: Pausing (calling wait())...");
lock.wait(); // thread is suspended
System.out.println("Thread 1: Resumed.");
} catch (InterruptedException e) { e.printStackTrace(); }
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("Thread 2: Holding lock...");
System.out.println("Thread 2: Pausing for 5 seconds...");
Thread.sleep(5000);
System.out.println("Thread 2: Calling notify()");
lock.notify(); // resume thread1
} catch (InterruptedException e) { e.printStackTrace(); }
}
});
thread1.start();
thread2.start();
}
}In Android, a simulated request shows that the main thread appears “suspended” while an I/O coroutine runs, but the main thread continues executing subsequent statements.
// Simulated request method
fun requestHttp(
requestParams : Map
,
block : (RequestResult) -> Unit
) = requestHttpReal(requestParams,block)
val requestParams = assembleRequestParams() // main thread
thread { // this block “suspends” the current thread
requestHttp(requestParams){ requestResult ->
runOnUiThread { updateUI(requestResult) } // “resume” on UI thread
}
}
doSomethingElse()The article clarifies that the main thread is not truly paused; the code after the coroutine still runs.
Coroutine Suspension and Resumption
A proper coroutine suspension example uses the suspend modifier and withContext to switch dispatchers.
suspend fun requestHttp(
requestParams : Map
,
block : (RequestResult) -> Unit
) = requestHttpReal(requestParams,block)
val requestParams = assembleRequestParams()
GlobalScope.launch { // Coroutine‑1 on main thread
val requestResult = withContext(Dispatchers.IO) { // Coroutine‑2 on IO thread, suspends Coroutine‑1
requestHttp(requestParams) // non‑blocking wait
}
updateUI(requestResult) // resume Coroutine‑1 on main thread
}
doSomethingElse()The withContext call suspends the outer coroutine, records its continuation, switches to an IO thread, and resumes the outer coroutine when the inner block completes, effectively turning asynchronous code into sequential style.
GlobalScope.launch creates Coroutine‑1.
withContext creates Coroutine‑2.
withContext suspends Coroutine‑1, records its state, and switches threads.
After the IO work finishes, the framework resumes Coroutine‑1 on the original thread.
Code outside the coroutine (e.g., doSomethingElse() ) runs immediately because it is not part of the suspended flow.
Underlying Mechanism of suspend
The article walks through the low‑level coroutine construction:
// 1. Define a suspend lambda (the coroutine body)
val f: suspend () -> Int = {
log("In Coroutine.")
999
}
// 2. Provide a Continuation to receive the result
val completion = object : Continuation
{
override fun resumeWith(result: Result
) {
log("Coroutine End: $result")
val resultOrThrow = result.getOrThrow()
}
override val context: CoroutineContext = EmptyCoroutineContext
}
// 3. Create the coroutine
val createCoroutine = f.createCoroutine(completion)
// 4. Start it
createCoroutine.resume(Unit)When resume(Unit) is called, the coroutine runs until it hits a suspend point (e.g., delay , withContext , suspendCoroutine ), at which moment the framework captures the continuation and returns control to the caller. The captured continuation is later invoked via resumeWith , completing the suspension‑resumption cycle.
Key suspend‑capable APIs such as delay , withContext , and suspendCoroutine ultimately delegate to suspendCoroutineUninterceptedOrReturn , which throws a special exception to signal suspension to the coroutine runtime.
Conclusion
What Is a Kotlin Coroutine?
A Kotlin coroutine is a structured code unit similar to a Runnable or Future, but it is managed by the Kotlin compiler and coroutine library, providing a rich API for asynchronous, non‑blocking programming while preserving a sequential coding style.
What Are Suspension and Resumption?
Within a coroutine scope, suspension pauses the current coroutine and runs another coroutine; when the latter finishes, the original coroutine is resumed via a callback that the compiler generates automatically.
What Is the suspend Keyword For?
The suspend modifier marks a function as potentially suspendable; actual suspension occurs only if the function calls other suspend‑capable APIs (e.g., delay , withContext , suspendCoroutine ), which internally invoke suspendCoroutineUninterceptedOrReturn .
Are Coroutines More Efficient Than Threads?
Coroutines run on top of threads, so comparing raw efficiency is meaningless; they are lightweight constructs that let developers write asynchronous code without the overhead of managing raw threads.
Why Choose Coroutines?
They allow writing asynchronous code in a sequential style, avoiding callback hell.
They improve developer productivity by providing non‑blocking suspension APIs.
Kotlin’s structured concurrency prevents common concurrency bugs such as race conditions and deadlocks.
Advanced features like Channels and Flows help manage complex data streams.
Drawbacks
The main drawback is the learning curve; developers need a solid understanding of coroutine internals to use them correctly and reap their benefits.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.