Coroutines support libraries for Kotlin providing structured concurrency primitives, Flow API for reactive streams, channels for communication, and synchronization utilities across all Kotlin platforms
—
Thread management and execution contexts for controlling where coroutines run. Dispatchers provide the threading policy for coroutine execution and are a key component of the coroutine context.
The foundation class for all coroutine dispatchers, providing thread management and execution control.
/**
* Base class to be extended by all coroutine dispatcher implementations.
*/
abstract class CoroutineDispatcher : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
/**
* Dispatches execution of a runnable block onto another thread in the given context.
* This method should not be used directly.
*/
abstract fun dispatch(context: CoroutineContext, block: Runnable)
/**
* Returns true if the execution of the coroutine should be performed with dispatch method.
* The default behavior for most dispatchers is to return true.
*/
open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
/**
* Creates a view of the current dispatcher that limits the parallelism to the given value.
*/
fun limitedParallelism(parallelism: Int): CoroutineDispatcher
/**
* Dispatches execution of a block onto another thread, but immediately returns
* without waiting for the completion.
*/
operator fun plus(other: CoroutineContext): CoroutineContext
}The built-in dispatcher implementations provided by kotlinx-coroutines.
/**
* Groups various implementations of CoroutineDispatcher.
*/
object Dispatchers {
/**
* The default CoroutineDispatcher that is used by all standard builders
* if no dispatcher is specified in their context.
*/
val Default: CoroutineDispatcher
/**
* A coroutine dispatcher that is confined to the Main thread operating with UI objects.
*/
val Main: MainCoroutineDispatcher
/**
* A coroutine dispatcher that is not confined to any specific thread.
*/
val Unconfined: CoroutineDispatcher
/**
* The IO dispatcher that is designed for offloading blocking IO tasks to a shared pool of threads.
* Available on JVM and Native targets.
*/
val IO: CoroutineDispatcher // Platform-specific availability
}
/**
* A CoroutineDispatcher for UI components that operate on the main/UI thread.
*/
abstract class MainCoroutineDispatcher : CoroutineDispatcher() {
/**
* Returns dispatcher that executes coroutines immediately when it is already
* in the right context (on main thread) instead of dispatching them.
*/
abstract val immediate: MainCoroutineDispatcher
}Usage Examples:
import kotlinx.coroutines.*
val scope = MainScope()
// Default dispatcher - for CPU-intensive work
scope.launch(Dispatchers.Default) {
val result = heavyComputation()
println("Computation result: $result")
}
// Main dispatcher - for UI operations (iOS main queue)
scope.launch(Dispatchers.Main) {
updateUI("Loading...")
// Switch to background for work
val data = withContext(Dispatchers.Default) {
fetchAndProcessData()
}
// Back to main for UI update
updateUI("Data: $data")
}
// Main.immediate - avoids dispatch if already on main
scope.launch(Dispatchers.Main.immediate) {
// This runs immediately if already on main thread
println("Quick UI update")
}
// Unconfined dispatcher - not recommended for general use
scope.launch(Dispatchers.Unconfined) {
println("Starts in calling thread: ${Thread.currentThread().name}")
delay(100)
println("Resumes in different thread: ${Thread.currentThread().name}")
}
// IO dispatcher (JVM/Native specific)
scope.launch(Dispatchers.IO) {
val fileContent = readFile("data.txt")
val networkResponse = makeNetworkCall()
processIOResults(fileContent, networkResponse)
}Customize dispatcher behavior and create limited parallelism views.
/**
* Creates a view of the current dispatcher that limits the parallelism to the given value.
* The resulting dispatcher shares threads with the original dispatcher.
*/
fun CoroutineDispatcher.limitedParallelism(parallelism: Int): CoroutineDispatcherUsage Examples:
import kotlinx.coroutines.*
val scope = MainScope()
// Limit parallelism for resource-constrained operations
val limitedDispatcher = Dispatchers.Default.limitedParallelism(2)
scope.launch {
// Only 2 coroutines can run concurrently on this dispatcher
repeat(10) { i ->
launch(limitedDispatcher) {
println("Task $i starting on ${Thread.currentThread().name}")
delay(1000) // Simulate work
println("Task $i completed")
}
}
}
// Create a dispatcher for database operations (limit to 1 for SQLite)
val databaseDispatcher = Dispatchers.IO.limitedParallelism(1)
suspend fun performDatabaseOperation(operation: String) {
withContext(databaseDispatcher) {
println("Performing $operation on ${Thread.currentThread().name}")
// Database operation here
delay(100)
}
}
scope.launch {
// All database operations will be serialized
repeat(5) { i ->
launch {
performDatabaseOperation("Operation $i")
}
}
}
// Network requests with limited concurrency
val networkDispatcher = Dispatchers.IO.limitedParallelism(4)
suspend fun makeNetworkRequest(url: String): String {
return withContext(networkDispatcher) {
println("Fetching $url on ${Thread.currentThread().name}")
delay(500) // Simulate network delay
"Response from $url"
}
}Change execution context during coroutine execution.
/**
* Calls the specified suspending block with a given coroutine context,
* suspends until it completes, and returns the result.
*/
suspend fun <T> withContext(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T
): TUsage Examples:
import kotlinx.coroutines.*
val scope = MainScope()
scope.launch {
println("Starting on: ${Thread.currentThread().name}")
// Switch to Default dispatcher for CPU work
val result = withContext(Dispatchers.Default) {
println("Computing on: ${Thread.currentThread().name}")
expensiveComputation()
}
println("Back on: ${Thread.currentThread().name}")
// Switch to Main for UI update
withContext(Dispatchers.Main) {
println("Updating UI on: ${Thread.currentThread().name}")
displayResult(result)
}
}
// Chain context switches
scope.launch(Dispatchers.Main) {
val userData = withContext(Dispatchers.IO) {
// Fetch from network
fetchUserData()
}
val processedData = withContext(Dispatchers.Default) {
// Process data
processUserData(userData)
}
// Back to main for UI
updateUserProfile(processedData)
}
// Preserve context elements while switching dispatcher
scope.launch(Dispatchers.Main + CoroutineName("UserLoader")) {
val result = withContext(Dispatchers.Default) {
// CoroutineName is preserved, only dispatcher changes
println("Processing in coroutine: ${coroutineContext[CoroutineName]?.name}")
heavyProcessing()
}
displayResult(result)
}iOS ARM64 specific dispatcher behavior and characteristics.
iOS Main Dispatcher:
// On iOS, Dispatchers.Main is backed by Darwin's main queue
scope.launch(Dispatchers.Main) {
// Executes on the main queue
// Safe for UIKit operations
updateUILabel("Hello iOS")
}
// Main.immediate avoids queue dispatch when already on main
scope.launch(Dispatchers.Main.immediate) {
// If already on main queue, executes immediately
// Otherwise dispatches to main queue
performImmediateUIUpdate()
}iOS Threading Characteristics:
import kotlinx.coroutines.*
// Default dispatcher uses background queues
scope.launch(Dispatchers.Default) {
// Uses global concurrent queue with QOS_CLASS_DEFAULT
println("Running on background thread: ${Thread.currentThread().name}")
}
// For iOS-specific threading needs
suspend fun performIOSSpecificWork() {
withContext(Dispatchers.Default) {
// CPU-intensive work on background queue
val result = processLargeDataSet()
// Switch to main for UI updates
withContext(Dispatchers.Main) {
updateProgressIndicator(result.progress)
}
}
}Tools and techniques for testing and debugging dispatcher behavior.
import kotlinx.coroutines.*
import kotlinx.coroutines.test.*
// Testing with TestDispatchers (from kotlinx-coroutines-test)
@Test
fun testWithTestDispatcher() = runTest {
val testDispatcher = TestCoroutineScheduler()
launch(testDispatcher) {
delay(1000)
println("Task completed")
}
// Advance time in test
advanceTimeBy(1000)
}
// Debug dispatcher behavior
scope.launch {
println("Initial context: ${coroutineContext}")
withContext(Dispatchers.Default) {
println("In Default: ${coroutineContext[CoroutineDispatcher]}")
withContext(Dispatchers.Main) {
println("In Main: ${coroutineContext[CoroutineDispatcher]}")
}
}
}
// Monitor dispatcher performance
fun monitorDispatcherUsage() {
val dispatcher = Dispatchers.Default.limitedParallelism(2)
repeat(10) { i ->
scope.launch(dispatcher) {
val startTime = System.currentTimeMillis()
println("Task $i started at $startTime")
delay(1000)
val endTime = System.currentTimeMillis()
println("Task $i finished at $endTime, duration: ${endTime - startTime}ms")
}
}
}Guidelines for effective dispatcher usage in iOS applications.
Choosing the Right Dispatcher:
// UI operations - always use Main
scope.launch(Dispatchers.Main) {
updateLabel(text)
animateView()
}
// CPU-intensive work - use Default
scope.launch(Dispatchers.Default) {
val result = complexCalculation()
// Switch back to Main for UI updates
withContext(Dispatchers.Main) {
displayResult(result)
}
}
// File I/O and network calls - use IO (when available) or limited Default
val ioDispatcher = Dispatchers.Default.limitedParallelism(4)
scope.launch(ioDispatcher) {
val data = readFromFile()
val response = makeNetworkCall()
processResults(data, response)
}Avoiding Common Pitfalls:
// DON'T: Unnecessary context switching
scope.launch(Dispatchers.Main) {
withContext(Dispatchers.Main) { // Redundant
updateUI()
}
}
// DO: Use Main.immediate when appropriate
scope.launch(Dispatchers.Main.immediate) {
updateUI() // Executes immediately if already on main
}
// DON'T: Blocking operations on Main
scope.launch(Dispatchers.Main) {
Thread.sleep(1000) // Blocks main thread - BAD!
}
// DO: Switch context for blocking operations
scope.launch(Dispatchers.Main) {
val result = withContext(Dispatchers.Default) {
blockingOperation() // Runs on background thread
}
updateUI(result) // Back on main thread
}
// DON'T: Excessive context switching
scope.launch {
withContext(Dispatchers.Default) {
withContext(Dispatchers.Main) { // Unnecessary switching
withContext(Dispatchers.Default) {
computeValue()
}
}
}
}
// DO: Minimize context switches
scope.launch {
val result = withContext(Dispatchers.Default) {
computeValue()
}
withContext(Dispatchers.Main) {
updateUI(result)
}
}iOS-Specific Considerations:
// Handle iOS app lifecycle
class iOSCoroutineManager {
private val scope = MainScope()
fun startBackgroundWork() {
scope.launch(Dispatchers.Default) {
while (isActive) {
performBackgroundTask()
delay(30000) // 30 seconds
}
}
}
fun cleanup() {
scope.cancel()
}
}
// Coordinate with iOS async APIs
suspend fun bridgeToiOSAPI(): String = suspendCancellableCoroutine { continuation ->
iOSAsyncFunction { result, error ->
DispatchQueue.main.async {
if let error = error {
continuation.resumeWithException(error)
} else {
continuation.resume(result)
}
}
}
}Install with Tessl CLI
npx tessl i tessl/maven-org-jetbrains-kotlinx--kotlinx-coroutines-core