Unsplash@jeffashton_

Understanding Coroutines Dispatchers

Jaewoong Eum
ProAndroidDev
Published in
6 min readJan 24, 2025

Since Kotlin Coroutines have been widely adopted in many Android projects, they have continued to evolve, inspiring creative solutions for handling asynchronous programming. Coroutines, a language-level feature in Kotlin, enable non-blocking code execution, simplifying tasks like network requests, file I/O, and other long-running operations by allowing functions to pause and resume seamlessly without blocking the underlying thread.

However, this doesn’t mean you can entirely disregard the concept of threads. Coroutines still rely on threads under the hood to execute tasks; the key difference is that they manage to run these tasks without blocking the threads, offering a more efficient and streamlined approach to concurrency in Android development. That’s why, when using Coroutines, you can avoid dealing with the complexity of managing threads and related resources manually. Coroutines provide APIs, such as Dispatchers, that allow you to specify the thread or thread pool where the coroutine should execute.

In this article, you’ll delve into the concept of Dispatchers, coroutine contexts, and relevant knowledges featured in Dove Letter. Dove Letter is a subscription repository where you can learn, discuss, and share new insights about Android and Kotlin with industrial Android developer interview questions, tips with code, articles, discussion, and trending news. If you’re interested in joining, be sure to check out “Learn Kotlin and Android With Dove Letter.”

Understanding Coroutines Dispatchers

Before exploring Dispatchers, it’s crucial to first understand the concept of Coroutine Context, as Dispatchers are one of the Coroutine Context Elements. The following code snippet illustrates this relationship:

A Coroutine Context is an immutable collection of key-value pairs that defines various settings and behaviors for coroutine execution. It can include objects that handle threading policies, logging, security, transactions, coroutine identity, and names, making it a crucial tool for configuring the coroutine environment consistently and efficiently throughout its lifecycle.

Now, what are Dispatchers? Coroutine Dispatchers determine the thread or thread pool where a coroutine executes. They play a critical role in managing how and where coroutines run, allowing for efficient background processing, UI updates, and computational tasks. Kotlin’s Coroutine library provides five types of Dispatchers to meet different requirements:

  1. Dispatchers.Main: Used for UI-related tasks, ensuring coroutines run on the main thread, primarily used for updating UI components in applications. On the JVM, it adapts to the appropriate main thread mechanism, such as the Android main thread, JavaFx, or Swing EDT, depending on the environment.
  2. Dispatchers.Main.immediate: A variant of Dispatchers.Main that runs coroutines immediately on the main thread, eliminating the need for additional dispatching. This optimization enhances performance by reducing unnecessary context switching, particularly beneficial for UI updates that originate from the main thread. For example, LifecycleOwner.lifecycleScope is bound to Dispatchers.Main.immediate.
  3. Dispatchers.IO: Optimized for offloading blocking I/O-intensive operations to a shared thread pool. By default, it can utilize up to 64 threads + a number equal to the available CPU cores according to JetBrains. This parallelism level can be adjusted using the kotlinx.coroutines.io.parallelism system property. Additionally, Dispatchers.IO supports elasticity through the limitedParallelism function, allowing the creation of custom dispatchers with specific thread limits that share resources with the default Dispatchers.IO pool.
  4. Dispatchers.Default: Optimized for CPU-intensive tasks such as data processing, complex calculations, or large data manipulations. This is a shared pool of background threads used by standard builders like launch and async when no specific dispatcher is provided. By default, it utilizes a number of threads equal to the available CPU cores, with a minimum of two threads, to efficiently manage concurrent tasks.
  5. Dispatchers.Unconfined: Starts coroutines in the calling thread but resumes them on the thread determined by the next suspending function. It’s useful for testing or lightweight tasks but should be used cautiously.

You can select the appropriate Dispatcher based on your specific use case, as outlined in the table below for easy comparison:

CPU-intensive tasks and I/O tasks

Now that you’ve explored the different types of Dispatchers and learned which one you need to choose for launching coroutines properly, but you might still have lingering questions. For instance, what exactly constitutes a “CPU-intensive task”? How does it differ from an I/O task? At first glance, the term “intensive” may suggest that CPU tasks demand more threads than I/O tasks due to their focus on computation, but the reality is more nuanced. Let’s break it down further.

Understanding CPU-Intensive Tasks
CPU-intensive tasks demand computational power and are constrained by the number of available CPU cores. These tasks include operations like encryption, image processing, video encoding, or data analysis. Because these tasks rely entirely on the CPU, adding more threads than the number of available cores often results in thread contention rather than improved performance.

For instance, the optimal number of threads for CPU-intensive tasks generally aligns with the number of CPU cores. On an 8-core system, using eight threads is ideal for such tasks. Overloading threads for CPU-bound operations can lead to excessive context switching, negatively impacting performance. The key takeaway is to carefully align thread usage with the system’s computational capacity.

Understanding I/O-Intensive Tasks
I/O-intensive tasks, on the other hand, involve waiting for external resources such as network responses, disk I/O, or database queries. Unlike CPU-bound tasks, I/O-bound tasks spend most of their time idle, waiting for data to be transferred, rather than actively utilizing the CPU.

These tasks benefit from using more threads than there are CPU cores, as multiple I/O operations can run concurrently without causing CPU contention. For example, a system with 8 cores might efficiently manage dozens or even hundreds of I/O threads. Asynchronous programming models, like Kotlin Coroutines, further optimize resource utilization by enabling a smaller number of threads to handle numerous I/O-bound tasks. This approach significantly reduces resource overhead while improving scalability and efficiency.

Thread Behavior
Thread management strategies differ for CPU-intensive and I/O-intensive tasks. For CPU-bound tasks, thread counts should match the available cores for optimal performance. For I/O-bound tasks, leveraging more threads or using asynchronous programming (e.g., coroutines) ensures efficient resource utilization without overwhelming the CPU as you’ve seen in the illustration below:

Now you have a clearer understanding of Dispatchers, especially when and how to choose the appropriate one for running specific coroutine tasks effectively.

Conclusion

In this article, you’ve explored the concept of Dispatchers, coroutine contexts, and what are the differences between CPU-intensive tasks and I/O tasks. As always deeper understanding makes you decide more concise in many different situations, especially the solutions that you use friendly in your daily life. The internal mechanism of Coroutines is quite complex, so don’t need to rush, just keep learning the surfaces and learn one by one, and not be intimidated.

This topic initially has been covered in Dove Letter, a private repository offering daily insights on Android and Kotlin, including topics like Compose, architecture, industry interview questions, and practical code tips. In just 20 weeks since its launch, Dove Letter has surpassed 400 individual subscribers and 12business/lifetime subscribers. If you’re eager to deepen your knowledge of Android, Kotlin, and Compose, be sure to check out ‘Learn Kotlin and Android With Dove Letter’.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Published in ProAndroidDev

The latest posts from Android Professionals and Google Developer Experts.

Written by Jaewoong Eum

Senior Android Developer Advocate @ Stream 🥑 • GDE for Android • OSS engineer. Love psychology, magic tricks, and writing poems. https://github.com/skydoves

Responses (3)

Write a response