kotlinintermediate

Coroutine Dispatchers and Context

Choose the right dispatcher: Default, IO, Main, Unconfined, and custom thread pools for coroutines.

kotlin
import kotlinx.coroutines.*
import java.util.concurrent.Executors
import kotlin.system.measureTimeMillis

fun main() = runBlocking {
    // Show which thread each dispatcher uses
    println("--- Dispatcher Threads ---")

    launch(Dispatchers.Default) {
        println("Default: ${Thread.currentThread().name}")
    }
    launch(Dispatchers.IO) {
        println("IO: ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) {
        println("Unconfined (before): ${Thread.currentThread().name}")
        delay(1)
        println("Unconfined (after): ${Thread.currentThread().name}")
    }

    delay(100)

    // Default — CPU-intensive work
    println("\n--- CPU Work (Default) ---")
    val cpuTime = measureTimeMillis {
        val results = (1..8).map { n ->
            async(Dispatchers.Default) {
                // Simulate CPU work
                var sum = 0L
                repeat(10_000_000) { sum += it }
                sum
            }
        }.awaitAll()
        println("Computed ${results.size} results")
    }
    println("CPU time: ${cpuTime}ms")

    // IO — blocking I/O operations
    println("\n--- IO Operations ---")
    val ioTime = measureTimeMillis {
        val files = (1..20).map { i ->
            async(Dispatchers.IO) {
                delay(100) // simulate file/network I/O
                "File-$i content"
            }
        }.awaitAll()
        println("Read ${files.size} files")
    }
    println("IO time: ${ioTime}ms (parallel!)")

    // withContext — switch dispatcher
    println("\n--- withContext ---")
    suspend fun fetchAndProcess(): String {
        // IO for network call
        val data = withContext(Dispatchers.IO) {
            println("  Fetching on: ${Thread.currentThread().name}")
            delay(100)
            "raw-data"
        }
        // Default for CPU processing
        val processed = withContext(Dispatchers.Default) {
            println("  Processing on: ${Thread.currentThread().name}")
            data.uppercase()
        }
        return processed
    }
    println("Result: ${fetchAndProcess()}")

    // Custom dispatcher
    println("\n--- Custom Dispatcher ---")
    val customDispatcher = Executors.newFixedThreadPool(2) { r ->
        Thread(r, "custom-pool").apply { isDaemon = true }
    }.asCoroutineDispatcher()

    val customResults = (1..4).map { i ->
        async(customDispatcher) {
            println("  Task $i on: ${Thread.currentThread().name}")
            delay(50)
            "result-$i"
        }
    }.awaitAll()
    println("Custom results: $customResults")
    customDispatcher.close()

    // limitedParallelism (Kotlin 1.6+)
    println("\n--- Limited Parallelism ---")
    val limited = Dispatchers.IO.limitedParallelism(2)
    val limitedTime = measureTimeMillis {
        (1..6).map { i ->
            async(limited) {
                println("  Limited task $i on: ${Thread.currentThread().name}")
                delay(100)
            }
        }.awaitAll()
    }
    println("Limited time: ${limitedTime}ms (6 tasks, 2 at a time)")

    // CoroutineContext elements
    println("\n--- Context Elements ---")
    val context = Dispatchers.Default + CoroutineName("my-coroutine")
    launch(context) {
        println("Name: ${coroutineContext[CoroutineName]?.name}")
        println("Job: ${coroutineContext[Job]}")
        println("Dispatcher: ${coroutineContext[ContinuationInterceptor]}")
    }

    delay(100)
}

Use Cases

  • Choosing optimal dispatcher for workload type
  • Thread pool management for I/O vs CPU
  • Context switching between dispatchers

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.