Irbisa · Cheatsheet
Kotlin Coroutines
Suspending functions, scopes, dispatchers, structured concurrency, cancellation, exception handling
What are coroutines, how do they differ from threads, and why are they called lightweight?
fun main() = runBlocking { repeat(100_000) { // 100k threads would run out of memory launch { delay(1_000) // suspends: the thread serves other coroutines print(".") }…What do
runBlockingandGlobalScopedo, and why don't they belong in Android app code?What is the difference between
launchandasync, and how do they handle exceptions differently?class ProfileVM(private val api: Api) : ViewModel() { fun save(p: Profile) { viewModelScope.launch { api.save(p) } // Job, no result } fun load() = viewModelScope.launch {…What is structured concurrency, and why does it matter on Android?
What is the difference between
CoroutineScopeandCoroutineContext, and which elements does a context hold?Which dispatcher do you pick for CPU work, blocking I/O and UI updates, and how do
Dispatchers.DefaultandDispatchers.IOdiffer?class ReportRepo( private val dao: ReportDao, private val io: CoroutineDispatcher = Dispatchers.IO, // injectable for tests) { private val writer = io.limitedParallelism(1) // one writer at a time…What does the
suspendkeyword actually do, and what does the compiler turn a suspend function into?// What you writesuspend fun showProfile(id: Long) { val user = api.user(id) // suspension point 1 val posts = api.posts(user.id) // suspension point 2 render(user, posts)}…When do you use
withContextand whenasync/await, and how do you run three independent network calls in parallel?suspend fun loadDashboard(): Dashboard = coroutineScope { val user = async { api.user() } val feed = async { api.feed() } val badges = async { api.badges() } Dashboard(user.await(), feed.await(), badges.await()) // all three overlap}…Will the
try/catchin this code catch the exception, and how do you handle exceptions in coroutines correctly?fun load() { try { viewModelScope.launch { val feed = api.feed() // throws IOException _state.value = FeedState.Ready(feed) } } catch (e: IOException) { _state.value = FeedState.Error(e) }}How does coroutine cancellation work, why is it cooperative, and why must you never swallow
CancellationException?suspend fun hashAll(files: List<File>): List<String> = withContext(Dispatchers.Default) { files.map { file -> ensureActive() // CPU loop: stop here if cancelled sha256(file) }}…How do
viewModelScope,lifecycleScopeandrepeatOnLifecyclediffer, and why waslaunchWhenStarteddeprecated?class ChatFragment : Fragment(R.layout.fragment_chat) { private val vm: ChatViewModel by viewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {…How do you wrap a callback-based API in a suspend function or a
Flow?suspend fun Call.await(): Response = suspendCancellableCoroutine { cont -> enqueue(object : Callback { override fun onResponse(call: Call, response: Response) = cont.resume(response) override fun onFailure(call: Call, e: IOException) = cont.resumeWithException(e) }) cont.invokeOnCancellation { cancel() } // cancel the HTTP call too…A thousand coroutines each increment the same counter a thousand times and the total comes out below a million - why, and how do you fix it without blocking a thread?
suspend fun broken(): Int { var counter = 0 withContext(Dispatchers.Default) { repeat(1_000) { launch { repeat(1_000) { counter++ } } } // races: usually less than 1_000_000 } return counter…Coroutines and Flow vs RxJava: what are the trade-offs, and how do their thread pools differ?
What do
SupervisorJobandsupervisorScopechange, and why doesscope.launch(SupervisorJob()) { }break cancellation?val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) // wrong: the passed Job becomes the parent, this is not a child of scopeval orphan = scope.launch(SupervisorJob()) { longRunningSync() }// scope.cancel() leaves it running; only orphan.cancel() stops it…api.promo()throws andpromo.await()is never called. Does this code crash, and what happens to the feed?fun refresh() { viewModelScope.launch { val promo = async { api.promo() } // throws IOException val feed = api.feed() _state.value = FeedState.Ready(feed) // promo.await() is never called }}Find the bug: the first API call that needs a token hangs forever, and so does every call after it.
class TokenProvider(private val auth: AuthApi) { private val mutex = Mutex() private var token: Token? = null suspend fun validToken(): Token = mutex.withLock { val current = token if (current != null && !current.isExpired()) current else refresh() } suspend fun refresh(): Token = mutex.withLock { auth.refresh().also { token = it } }}