Dispatchers & Threading Flashcards
KOTLIN › Coroutines
- What does a CoroutineDispatcher actually do, and when does it act?
- It decides which thread runs a coroutine, by receiving a Runnable through dispatch() and scheduling it. It acts at resumption points only: when a coroutine starts and each time a suspension resolves. While suspended, a coroutine occupies no thread at all.
- Are Dispatchers.IO and Dispatchers.Default separate thread pools?
- No. They share one pool and differ in how many threads each may occupy. Default is capped at the CPU core count (floor of 2); IO is capped at 64 (or the core count if higher) and can grow the shared pool to reach it.
- Why is Dispatchers.IO allowed 64 threads when Default is capped at core count?
- IO threads spend their time blocked, not computing, so a core-count cap would throttle throughput for nothing. CPU-bound work genuinely cannot exceed the cores available, so more parallelism on Default would only add context switching.
- What does Dispatchers.IO.limitedParallelism(4) allocate?
- Nothing new. It is a view over the parent dispatcher that refuses to dispatch more than four coroutines concurrently, borrowing threads from the pool that already exists. There is nothing to close, unlike an executor-backed dispatcher.
- What does limitedParallelism(1) give you, and why is it useful?
- A context that runs one coroutine at a time. State touched only from inside it needs no lock, atomic, or @Volatile: execution is serialised and the dispatch itself supplies the happens-before edge. It is thread confinement without owning a thread.
- What does Dispatchers.Unconfined really do?
- It does not dispatch. The coroutine body runs inline on the calling thread, immediately, until the first suspension point; after that it resumes on whichever thread completed the suspending call. It is for tests and framework code, not application code.
- What is the difference between Dispatchers.Main and Dispatchers.Main.immediate?
- Plain Main always posts to the main Looper, even when you are already on the main thread, costing a message-queue round trip. Main.immediate checks first and runs inline if the thread is already correct. Lifecycle-aware collection helpers use immediate to avoid a wasted frame.
- After withContext(Dispatchers.IO) { } completes, which context does the caller resume on?
- The one it started with. withContext switches for the duration of its block only and then returns execution to the original context. That round trip is exactly what makes a suspend function main-safe without its callers knowing anything about threading.
- Should a suspend function that never blocks be wrapped in withContext(Dispatchers.IO)?
- No. Main-safety means not blocking the caller, and a function that only awaits other suspend functions already satisfies that. The switch costs a dispatcher round trip for nothing. Put the switch at the innermost point where blocking actually happens.
- What obligation comes with Executors.newFixedThreadPool(4).asCoroutineDispatcher()?
- You own real threads and must close() the dispatcher (or shut down the executor), or they run for the life of the process. It also sits outside the shared pool, so its threads idle when unused. Prefer limitedParallelism unless you genuinely need stable, application-owned threads.
- Why can Dispatchers.Main not simply be injected into a ViewModel for tests?
- Because viewModelScope hard-codes Dispatchers.Main inside the lifecycle library, so no constructor parameter of yours reaches it. Dispatchers.setMain(testDispatcher) replaces the platform main dispatcher globally, usually via a JUnit rule.
- Why hoist withContext out of a loop?
- Each call builds a context, may hand off to another thread, suspends, and dispatches back. Once, that is irrelevant; per iteration over a thousand rows it dominates. Switch once around the whole batch instead of once per item.
- When do you use Dispatchers.Main vs IO vs Default?
- Main: UI updates and interacting with the main thread. IO: blocking I/O such as network, disk, and database (large elastic pool). Default: CPU-intensive work like sorting, parsing, or JSON processing (pool sized to CPU cores).
- What is withContext and how does it make a function main-safe?
- withContext(context) suspends, switches the coroutine to the given dispatcher/context, runs the block, returns the result, and switches back. Wrapping blocking work in withContext(Dispatchers.IO) inside a suspend function makes it safe to call from the main thread.
- What is CoroutineName and how do you combine it with a dispatcher?
- CoroutineName("label") is a context element that names the coroutine for debugging. Combine it with other elements using the + operator: Dispatchers.IO + CoroutineName("fetch"). Each element has a unique key, so + merges without overwriting unrelated elements.