What is Coroutines in Kotlin

Allows to call blocking functions avoiding nested callbacks, makes threading easy. Coroutines allows computations to be suspended without blocking the main thread. Its basically like a Runnable with super powers. It helps to express ashynscronicity using sequential code. Other benefits include exception handling and cancellation.

Add support for Coroutines by adding the following to your build.gradle file

dependencies {
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
}

Keywords

  • suspend - denotes a function has to be run as a coroutine
  • Continuation-passing style (done by the compiler)
  • withContext - suspend function from Kotlin lib, takes a dispatcher
  • launch - triggers a coroutine, must be called within a scope
  • Scope - check Structured concurrency below

Dispatchers available

  • .Main - for UI/non blocking code
  • .IO - for network and disk, main safe (can call from the main thread)
  • .Default - for CPU intensive computations

Structured concurrency

  • Scope - Keeps track of coroutines it created, is cancellable, gets notified about failures/exceptions
  • Scope is the parent of all coroutines it creates
  • Cancelling a scope cancels all of its child coroutines
  • suspend functions should be run in a scope using launch
  • When a suspend function returns it has completed all its work
  • Scope can accept a Job and jobs can affect the lifecycle of the scope and its coroutines. For a SupervisorJob a failure does not affect other children of the scope.

Launch vs Async

  • Both needs a Dispatcher
  • Both are entry points for coroutines
  • launch is best for fire and forget coroutines
  • async can be used to start a coroutine expecting a return value, async returns a deferred object on which await() can be called to suspend the coroutine and till it returns a value.
  • launch throws exceptions as it happens, async holds on to the exceptions until await is called.
  • for launch wrap the call to coroutine inside a try catch block.
  • for async only the await call needs to be wrapped in a try catch block.

Tips

  • Cancellation needs co-operation, especially when running heavy computation, use yield() or ensureActive() before expensive tasks in a coroutine to ensure co-operation.
  • Use suspend keyword wisely, a function using scope.launch doesn't need to be a suspend function but a function calling another suspend function or CoroutineScope directly needs to be a suspend function.

Testing

  • Use runBlocking to test a suspend function that doesn't trigger new coroutine using launch
  • For functions that trigger a new coroutine using launch cannot be tested using runBlocking it  can still be tested by waiting for the result but that slows down the test. The cleaner way is to inject dispatchers in this case. Inject a TestCoroutineDispatcher from the test and wrap the test with a  runBlockingTest on the test dispatcher.
  • Use testDispatcher pauseDispatcher and resumeDispatcher to test code that runs before the coroutine.

Resources

  • Testing coroutines - ADS 2019
  • Cancellation and Exceptions in coroutines - KotlinConf 2019