Kotlin Coroutines: Advanced Concepts and Dispatchers

Kotlin implements coroutines as a language feature for managing asynchronous and concurrent operations. Dispatchers define the thread pools where coroutines execute.

  • Dispatchers.IO: Manages I/O-bound tasks like file operations or network requests, using a dedicated thread pool to prevent blocking the main thread.
  • Dispatchers.Main: Handles UI-related operations, ensuring they run on the main thread.
  • Dispatchers.Unconfined: Starts execution in the calling thread and resumes in whichever thread the suspending function uses.
  • Dispatchers.Default: Optimized for CPU-intensive tasks like data processing, using a shared thread pool.
import kotlinx.coroutines.*

fun main() {
    CoroutineScope(Dispatchers.IO).launch {
        performTask()
    }
    println("Main thread continues")
    Thread.sleep(2000)
}

suspend fun performTask() {
    println("Task started")
    delay(1000)
    println("Task finished")
}

runBlocking

This builder creates a coroutine and blocks the current thread until completion, primarily used in tests or main functions.

import kotlinx.coroutines.*

fun main() {
    println("Start")
    val result = runBlocking(Dispatchers.IO) {
        println("Running on: ${Thread.currentThread().name}")
        delay(2000)
        42
    }
    println("Result: $result")
}

Within a runBlocking scope, you can launch additional coroutines.

import kotlinx.coroutines.*

fun main() {
    println("Start")
    val value = runBlocking(Dispatchers.IO) {
        println("Outer coroutine on: ${Thread.currentThread().name}")
        
        val childJob = launch {
            println("Child coroutine on: ${Thread.currentThread().name}")
            delay(5200)
        }
        
        println("Job active: ${childJob.isActive}")
        childJob.cancel()
        println("Job active after cancel: ${childJob.isActive}")
        
        delay(2000)
        99
    }
    println("Final value: $value")
}

withContext can switch dispatchers within a coroutine, useful for updating UI after a background operation.

import kotlinx.coroutines.*

fun main() {
    println("Start")
    val outcome = runBlocking(Dispatchers.IO) {
        performNetworkRequest {
            withContext(Dispatchers.Main) {
                // Update UI here
            }
        }
    }
    println("Outcome: $outcome")
}

suspend fun performNetworkRequest(onComplete: suspend () -> Unit) {
    delay(3000)
    println("Request succeeded")
    onComplete()
}

GlobalScope

GlobalScope launches coroutines with an application-wide lifecycle, which can lead to resource leaks if not managed carefully.

import kotlinx.coroutines.*

fun main() {
    val job1 = GlobalScope.launch {
        delay(3000)
        println("Coroutine finished")
    }
    val job2 = GlobalScope.launch { }
    println("Jobs same instance: ${job1 === job2}")
    // Keep main thread alive
    while (true);
}

CoroutineScope

Creating a custom CoroutineScope is recommended for better lifecycle management.

import kotlinx.coroutines.*

fun main() {
    val customScope = CoroutineScope(Dispatchers.Default)
    
    customScope.launch {
        delay(3000)
        println("First coroutine done")
    }
    customScope.launch {
        delay(3000)
        println("Second coroutine done")
    }
    
    customScope.cancel() // Cancels all coroutines in this scope
    Thread.sleep(4000)
    println("Main thread ends")
}

Launch Parameters

Context Parameter

import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

fun main() {
    runBlocking {
        val job = this.launch(Dispatchers.Default) {
            println("Dispatcher: ${coroutineContext[CoroutineDispatcher]}")
        }
        println(job)
    }
}

Start Parameter

CoroutineStart defines the launch behavior:

  • DEFAULT: Schedules execution according to the dispatcher.
  • ATOMIC: Starts immediately and cannnot be cancelled before begininng.
  • LAZY: Starts only when explicitly triggered via start() or await().
import kotlinx.coroutines.*

fun main() {
    runBlocking(Dispatchers.IO) {
        val lazyJob = launch(start = CoroutineStart.LAZY) {
            println("Lazy job executing")
            delay(1000)
            println("Lazy job done")
        }
        println("Triggering lazy job")
        lazyJob.start()
    }
}

Async for Concurrent Results

async returns a Deferred value, which can be awaited for a result.

import kotlinx.coroutines.*

fun main() {
    val scope = CoroutineScope(Dispatchers.Default)
    val deferredValue = scope.async {
        println("Async task started")
        delay(1000)
        "Result from async"
    }
    
    runBlocking {
        Thread.sleep(3000)
        val result = deferredValue.getCompleted()
        println(result)
    }
}

Awaiting a result within a coroutine.

import kotlinx.coroutines.*

fun main() {
    runBlocking {
        val deferred = async {
            computeValue()
        }
        repeat(10) { i ->
            println("Iteration $i")
        }
        val value = deferred.await()
        println("Computed value: $value")
    }
}

suspend fun computeValue(): Int {
    println("Computing...")
    delay(3000)
    return 100
}

Select Expresion

The select expression can wait for the first result from multiple concurrent operations.

import kotlinx.coroutines.*
import kotlinx.coroutines.selects.select

fun main() {
    runBlocking {
        val taskA = async {
            delay(2000)
            compute(1)
        }
        val taskB = async {
            compute(2)
        }
        
        val firstResult = select<String> {
            taskA.onAwait { "Task A completed" }
            taskB.onAwait { "Task B completed" }
        }
        println(firstResult)
        println("End")
    }
}

suspend fun compute(id: Int): Int {
    println("Compute $id executing")
    delay(3000)
    return 100
}

withContext for Dispatcher Switching

withContext switches to a different dispatcher and returns a value, suspending the calling coroutine.

import kotlinx.coroutines.*

fun main() {
    runBlocking(Dispatchers.IO) {
        println("Initial context: ${coroutineContext[CoroutineDispatcher]}")
        val result = withContext(Dispatchers.Default) {
            delay(2000)
            println("New context: ${coroutineContext[CoroutineDispatcher]}")
            "Result from withContext"
        }
        println(result)
        println("Back to initial context: ${coroutineContext[CoroutineDispatcher]}")
    }
}

To avoid blocking the outer coroutine, launch a new coroutine for the context switch.

import kotlinx.coroutines.*

fun main() {
    runBlocking(Dispatchers.IO) {
        println("Initial context: ${coroutineContext[CoroutineDispatcher]}")
        launch {
            val result = withContext(Dispatchers.Default) {
                delay(2000)
                println("New context: ${coroutineContext[CoroutineDispatcher]}")
                "Result from withContext"
            }
            println(result)
        }
        println("Outer coroutine continues")
    }
}

Suspend Functions

The suspend modifier marks a function that can pause coroutine execution without blocking the underlying thread. delay suspends the coroutine, allowing other coroutines on the same thread to proceed, unlike Thread.sleep which halts the entire thread.

Tags: kotlin Coroutines Asynchronous Programming Dispatchers Concurrency

Posted on Mon, 18 May 2026 20:47:58 +0000 by AutomatikStudio