Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tackling Asynchrony with Kotlin Coroutines

Praveer Gupta
November 24, 2019

Tackling Asynchrony with Kotlin Coroutines

This is the speaker's deck for the talk presented at Tech Triveni 2.0. (https://techtriveni.com/speaker-praveer)

Reactive Programming is an approach to asynchronous programming. Like every other tool, reactive programming tools like RxJava or Reactor come with their own challenges. Kotlin Coroutines alleviates may of these challenges. Kotlin Coroutines, when being designed, had the advantage of being able to explore existing code that uses reactive stream.

This talk will highlight some of these practical challenges and show how Kotlin Coroutines tackle them. The challenges that will be covered are changes in interface design, programming paradigm shift, reactive type selection, exception handling, cancellation, resource leakage, and sequential vs concurrent.

Praveer Gupta

November 24, 2019
Tweet

More Decks by Praveer Gupta

Other Decks in Technology

Transcript

  1. Reactive approach to Async fun getUserAddress(userId: Long): Single<Address> { …

    } fun getRestaurants(address: Address): Single<List<Restaurant>> { … } Wrapped return types
  2. Kotlin Coroutine’s approach to Async suspend fun getUserAddress(userId: Long): Address

    { … } suspend fun getRestaurants(address: Address): List<Restaurant> { … } Suspend modifier
  3. Sync API Usage fun showUserRestaurants(userId: Long) { val address =

    getUserAddress(userId) val restaurants = getRestaurants(address) processRestaurants(restaurants) } fun getUserAddress(userId: Long): Address { … } fun getRestaurants(address: Address): List<Restaurant> { … } Sequential & blocking
  4. fun showUserRestaurants(userId: Long) { getUserAddress(userId) .flatMap { address -> getRestaurants(address)

    } .subscribe { restaurants -> processRestaurants(restaurants) } } Reactive API Usage fun getUserAddress(userId: Long): Single<Address> { … } fun getRestaurants(address: Address): Single<List<Restaurant>> { … } Chained operators
  5. Kotlin Coroutine API Usage suspend fun showUserRestaurants(userId: Long) { val

    address = getUserAddress(userId) val restaurants = getRestaurants(address) processRestaurants(restaurants) } suspend fun getUserAddress(userId: Long): Address { … } suspend fun getRestaurants(address: Address): List<Restaurant> { … } Suspend modifier
  6. Comparison suspend fun showUserRestaurants(userId: Long) { val address = getUserAddress(userId)

    val restaurants = getRestaurants(address) processRestaurants(restaurants) } suspend fun getUserAddress(userId: Long): Address { … } suspend fun getRestaurants(address: Address): List<Restaurant> { … } fun showUserRestaurants(userId: Long) { val address = getUserAddress(userId) val restaurants = getRestaurants(address) processRestaurants(restaurants) } fun getUserAddress(userId: Long): Address { … } fun getRestaurants(address: Address): List<Restaurant> { … } Synchronous Asynchronous using Kotlin
 Coroutines
  7. Structure of imperative synchronous code is the same as asynchronous

    code (taken from KotlinConf 2018 - Exploring Coroutines in Kotlin by Venkat Subramaniam available on YouTube) Kotlin Coroutines
  8. Kotlin Coroutines No restructuring of code to a different paradigm

    Reuse existing language control flows Sequential vs Concurrent execution with coroutines Complexities of async programming that coroutines handles
  9. Kotlin Coroutines 101 import kotlinx.coroutines.* fun main() = runBlocking {

    launch { delay(1000L) println("World!") } println("Hello") } Output: Hello World!
  10. import kotlinx.coroutines.* fun main() = runBlocking { launch { delay(1000L)

    println("World!") } println("Hello") } Coroutine Builders Coroutine - an instance of a suspendable computation Kotlin Coroutines 101
  11. import kotlinx.coroutines.* fun main() = runBlocking { launch { delay(1000L)

    println("World!") } println("Hello") } Suspending function suspends the execution of the coroutine Kotlin Coroutines 101 public suspend fun delay(timeMillis: Long) { … }
  12. Suspended coroutine resumes with a continuation block public interface Continuation<in

    T> { public val context: CoroutineContext public fun resumeWith(result: Result<T>) } import kotlinx.coroutines.* fun main() = runBlocking { launch { delay(1000L) println("World!") } println("Hello") } Kotlin Coroutines 101
  13. import kotlinx.coroutines.* fun main() = runBlocking { launch { doWorld()

    } println("Hello") } private suspend fun doWorld() { delay(1000L) println("World!") } Suspending function is marked with a suspend modifier Kotlin Coroutines 101
  14. suspend fun showUserRestaurants(userId: Long) { val address = getUserAddress(userId) val

    restaurants = getRestaurants(address) processRestaurants(restaurants) } Reuse existing Control flows How can you make the below method resilient to failures?
  15. suspend fun showUserRestaurants(userId: Long) { try { val address =

    withTimeout(200L) { getUserAddress(userId) } val restaurants = withTimeout(300L) { getRestaurants(address) } processRestaurants(restaurants) } catch (e: TimeoutCancellationException) { // handle exception here } } Exception handling using standard try-catch block Reuse existing Control flows
  16. Sequential vs Concurrent suspend fun showUserDetails(userId: Long) { val userInfo

    = getUserInfo(userId) val orders = getUserOrders(userId) combineAndBuild(userInfo, orders) } Two independent
 calls to
 external system suspend fun getUserInfo(userId: Long): UserInfo { … } suspend fun getUserOrders(userId: Long): List<Order> { … }
  17. suspend fun showUserDetails(userId: Long) { val userInfo = getUserInfo(userId) val

    orders = getUserOrders(userId) combineAndBuild(userInfo, orders) } Sequential vs Concurrent suspend fun getUserInfo(userId: Long): UserInfo { … } suspend fun getUserOrders(userId: Long): List<Order> { … } first suspension
  18. Sequential vs Concurrent suspend fun getUserInfo(userId: Long): UserInfo { …

    } suspend fun getUserOrders(userId: Long): List<Order> { … } suspend fun showUserDetails(userId: Long) { val userInfo = getUserInfo(userId) val orders = getUserOrders(userId) combineAndBuild(userInfo, orders) } second suspension
  19. Sequential vs Concurrent suspend fun getUserInfo(userId: Long): UserInfo { …

    } suspend fun getUserOrders(userId: Long): List<Order> { … } suspend fun showUserDetails(userId: Long) { val userInfo = getUserInfo(userId) val orders = getUserOrders(userId) combineAndBuild(userInfo, orders) } final call
  20. Sequential vs Concurrent suspend fun showUserDetails(userId: Long) { val userInfo

    = getUserInfo(userId) val orders = getUserOrders(userId) combineAndBuild(userInfo, orders) } suspend fun getUserInfo(userId: Long): UserInfo { … } suspend fun getUserOrders(userId: Long): List<Order> { … } sequential execution by default
  21. Sequential vs Concurrent suspend fun showUserDetails(userId: Long) { coroutineScope {

    val userInfo = async { getUserInfo(userId) } val orders = async { getUserOrders(userId) } combineAndBuild(userInfo.await(), orders.await()) } } suspend fun getUserInfo(userId: Long): UserInfo { … } suspend fun getUserOrders(userId: Long): List<Order> { … } concurrently executed suspension happens here
  22. Structured Concurrency suspend fun showUserDetails(userId: Long) { coroutineScope { val

    userInfo = async { getUserInfo(userId) } val orders = async { getUserOrders(userId) } combineAndBuild(userInfo.await(), orders.await()) } } When can a resource leakage happen?
  23. Structured Concurrency suspend fun showUserDetails(userId: Long) { coroutineScope { val

    userInfo = async { getUserInfo(userId) } val orders = async { getUserOrders(userId) } combineAndBuild(userInfo.await(), orders.await()) } } Exception in a coroutine results in its cancellation 1 2 3 4
  24. Kotlin Coroutines No restructuring of code to a different paradigm

    Reuse existing language control flows Sequential vs Concurrent execution with coroutines Complexities of async programming that coroutines handles