Sparround

Coroutines: suspend, scopes, dispatchers

A coroutine is a suspendable unit of computation: it can wait without blocking a thread. One thread can run thousands of coroutines.

Core concepts:

  • suspend fun — a suspendable function; callable only from a coroutine or another suspend function. suspend itself does not switch threads — it only marks suspension points.
  • CoroutineScope — manages coroutine lifetimes. Android ships ready scopes: viewModelScope, lifecycleScope.
  • Job — a coroutine's cancellable handle; a SupervisorJob keeps one child's failure from cancelling its siblings.
  • Dispatchers: Main (UI), IO (network/disk), Default (CPU-bound work).
  • structured concurrency — every coroutine belongs to a scope: cancelling the scope cancels all children, so no coroutine is ever "leaked".
kotlin
class AccountViewModel(private val repo: AccountRepository) : ViewModel() {

    fun loadBalance() {
        viewModelScope.launch {                  // Main dispatcher-də başlayır
            _uiState.update { it.copy(isLoading = true) }
            try {
                val balance = withContext(Dispatchers.IO) {
                    repo.fetchBalance()          // şəbəkə çağırışı IO thread pool-da
                }
                _uiState.update { it.copy(balance = balance, isLoading = false) }
            } catch (e: IOException) {
                _uiState.update { it.copy(error = "Şəbəkə xətası", isLoading = false) }
            }
        }
    }
}

The typical ViewModel pattern: IO work, result on Main

Questionlaunchasync
ReturnsJob (no result)Deferred<T> — result via await()
PurposeFire-and-forget workResults of parallel computations
Exception behaviorPropagates to the parent immediatelyHeld until await() is called

Cancellation is cooperative: a coroutine only "sees" cancellation at suspension points (delay, withContext, yield) or by checking isActive. An infinite while(true) loop with no suspension point never cancels. Swallowing CancellationException (catching without rethrowing) is a classic bug — always rethrow it.

🛠 Practice task

Write two suspend functions that "fetch" a balance and a transaction history (simulate each with delay(1500)).

  • Call them sequentially and time it (measureTimeMillis).
  • Then call them in parallel with async + await and time it again.
  • Throw an exception in one and observe the behavioural difference between launch and async.

Done when: you can explain both the ~3s → ~1.5s difference and the exception behaviour.

📚 Sources and documentation