Deep Dive into Dispatchers for Kotlin Coroutines

BHAVNA THACKER
4 min readApr 14, 2022

--

Learn what Dispatchers are, their types, specifics of Unconfined Dispatchers and main difference between IO and Default dispatchers.

Dispatchers in Kotlin Coroutines
“Dispatchers” by Bhavna Thacker

What are Dispatchers in Kotlin Coroutines?

When you launch a coroutine, you can specify the coroutine context. The coroutine context includes a coroutine dispatcher that determines what thread or threads the corresponding coroutine uses for its execution.

All coroutine builders like launch and async accept an optional CoroutineContext parameter that can be used to explicitly specify the dispatcher for the new coroutine and other context elements.

Types of Dispatchers

There are basically four types of Dispatchers:

  1. Dispatchers.Main: A coroutine dispatcher that is confined to the Main thread operating with UI objects. Usually such dispatcher is single-threaded.
  2. Dispatchers.Default: The default CoroutineDispatcher that is used by all standard builders like launch, async, etc. if no dispatcher nor any other ContinuationInterceptor is specified in their context.
  3. Dispatchers.IO: The CoroutineDispatcher that is designed for offloading blocking IO tasks to a shared pool of threads.
  4. Dispatchers.Unconfined: A coroutine dispatcher that is not confined to any specific thread. It executes initial continuation of the coroutine in the current call-frame and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without mandating any specific threading policy.

Looks like definitions are too theoretical and confusing! Don’t worry, examples make every line of definition above clear!

Case 1: Don’t specify the dispatcher:

If you run above, you get:

Current thread is: main

Because, When launch { ... } is used without parameters, it inherits the context (and thus dispatcher) from the CoroutineScope it is being launched from. In this case, it inherits the context of the main runBlocking coroutine which runs in the main thread.

Case 2: Specify the default dispatcher:

If you run above, you get:

Current thread is: DefaultDispatcher-worker-1

Case 3: Specify the IO dispatcher:

If you run above, you get:

Current thread is: DefaultDispatcher-worker-1

Case 4: Specify Unconfined Dispatcher:

If you run above, you get:

Current thread is: main

Looks like Dispatchers.Unconfined is a special dispatcher that also appears to run in the main thread, but that’s not true.

It starts a coroutine in the caller thread, but only until the first suspension point. There was no suspension point in above code, so it started in the caller (main) thread. But what if there is a suspension point like below:

If you run above, you get:

Current thread is: main
Current thread after delay is: kotlinx.coroutines.DefaultExecutor

So, from above output, now its definition is clear:

The Dispatchers.Unconfined coroutine dispatcher starts a coroutine in the caller thread, but only until the first suspension point. After suspension it resumes the coroutine in the thread that is fully determined by the suspending function that was invoked.

Would you like to guess output of below?

Current thread is: main
Current thread after delay is: main

Here, it inherits the context of the main runBlocking coroutine which runs in the main thread, both before and after the delay.

Difference between IO and Default Dispatcher

The main difference between these two is as below:

Default Dispatcher (preferred for operations you want to off-load from the main thread like CPU-intensive operations):

By default, the maximal level of parallelism used by this dispatcher is equal to the number of CPU cores, but is at least two. Level of parallelism X guarantees that no more than X tasks can be executed in this dispatcher in parallel.

IO Dispatcher (preferred for heavy IO operations like read/write files, uploading or decrypting/encrypting files):

The number of threads used by tasks in this dispatcher is limited by the value of “kotlinx.coroutines.io.parallelism” (IO_PARALLELISM_PROPERTY_NAME) system property. It defaults to the limit of 64 threads or the number of cores (whichever is larger).

Dispatchers.IO has a unique property of elasticity, that means, its pool is not limited to 64 as we can decide to limit it to as many threads as we want.

If you define:

// 100 threads for MySQL connection
val myMysqlDbDispatcher = Dispatchers.IO.limitedParallelism(100)
// 60 threads for MongoDB connection
val myMongoDbDispatcher = Dispatchers.IO.limitedParallelism(60)

The system may have up to 64 + 100 + 60 threads dedicated to blocking tasks during peak loads, but during its steady state there is only a small number of threads shared among Dispatchers.IO, myMysqlDbDispatcher and myMongoDbDispatcher.

Conclusion:

So you learnt role & types of dispatchers, specifics of Unconfined Dispatchers and difference between IO and default dispatchers.

Hope you enjoyed reading it, if yes, please leave a clap that’s my motivation and don’t forget to follow me for notifications on my future posts.

Happy coding till then :)

References:
1. https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/index.html
2. https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/src/Dispatchers.kt

--

--

BHAVNA THACKER

Android GDE. Youtuber — LearnAndroid. Senior Android Engineer @MEGA. FPE @raywenderlich.