To kick off the series on Kotlin Coroutines, I will explain the fundamental concepts in Coroutines along with practical code examples. Through this, I hope to provide you with a solid foundation in Coroutines so you can learn and apply them faster.
Concepts explained in this article:
1. Dispatchers
True to its name, a Dispatcher is responsible for dispatching and assigning one or more threads to execute a coroutine. The available Dispatcher types include:
- Dispatchers.Default: The default dispatcher used if not explicitly specified in the Scope Builder. It uses a shared pool of background threads and is ideal for CPU-intensive computations.
- Dispatchers.IO: Designed for
IO-intensive blocking operationssuch as file read/write or blocking socket I/O. - Dispatchers.Unconfined: A rather unusual dispatcher. The documentation notes that it is not commonly used in regular code ;)). This dispatcher is not confined to any specific thread—it executes the coroutine initially in the caller thread, and upon resuming after suspension, the resuming thread is decided by the suspending function.
- Specific ThreadPools created via
newSingleThreadContextornewFixedThreadPoolContext. - Any Executor converted via
asCoroutineDispatcher().
| |
2. Scope
Coroutines are always launched inside a CoroutineScope. The purpose is structured concurrency and resource management. Imagine running a heavy task inside one or more coroutines; if halfway through that task is no longer needed, you can simply call cancel() on the scope containing those coroutines. Another key feature is that a CoroutineScope can nest child CoroutineScopes.
GlobalScope
GlobalScope is considered the parent scope for the entire application. GlobalScope cannot be cancel()ed and exists throughout the application lifecycle. Using it is generally NOT RECOMMENDED. You can read The reason to avoid GlobalScope to understand why.
Scope Builder
As mentioned above, coroutines are launched using these Scope Builders:
runBlocking: Runs a new coroutine and blocks[1] the current thread until it completes. Do not use this function inside a coroutine because it is designed as a bridge between regular blocking code and libraries written in a suspending style; typically used in the
mainfunction and in tests.coroutineScope: Creates a new
CoroutineScope. This scope is a child of the outer scope but overrides its Job. It is designed forparallel decomposition—if any coroutine fails inside this scope, all other awaiting coroutines in this scope are also cancelled.launch: Creates a new
CoroutineScope, does not block[2] the current thread, and returns a Job.async: Creates a new
CoroutineScope, does not block2 the current thread, and returns a Deferred. Useasyncwhen you need a return value from the invocation, whereaslaunchis fire-and-forget.
| |
3. Context
Every coroutine in Kotlin has a context represented by an instance of the CoroutineContext interface. This context is a set of elements configuring the coroutine, of which the two primary components are Job and Dispatcher.
Context is immutable. However, you can use the plus operator to produce a combined new context.
| |
4. Suspending Function
Suspending Function is the backbone of Kotlin Coroutines. Functions inside a suspending function can pause execution without blocking1 the underlying thread. The thread executing the suspending function is released back to the JVM and can be used for other tasks.
Remember, a Suspending Function must run inside a coroutine to take effect ;))
| |
5. Job
As mentioned in Context, a Job is an essential element of the context. It allows you to cancel(), join(), or start() the corresponding coroutine. In addition, the job tracks the lifecycle states of a coroutine as shown below:
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
| |
6. Deferred
A Deferred is also a Job, but it holds a computation result once the coroutine completes. Deferred is created using async (as described in ScopeBuilder) or initialized directly with CompletableDeferred.
| |
I have uploaded the sample code to this repo. Thank you for reading this far!