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 many 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.
3. Reactive approach to Async
fun getUserAddress(userId: Long): Single<Address> { … }
fun getRestaurants(address: Address): Single<List<Restaurant>> { … }
Wrapped return types
4. Kotlin Coroutine’s approach to Async
suspend fun getUserAddress(userId: Long): Address { … }
suspend fun getRestaurants(address: Address): List<Restaurant> { … }
Suspend modifier
5. 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
6. 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
& actions
7. 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
8. 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
9. 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
10. 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
12. 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
13. import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
Suspending
function suspends
the execution of
the coroutine
Kotlin Coroutines 101
14. 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
15. 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
17. 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?
18. 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
19. 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> { … }
20. 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
21. 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
22. 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
23. 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
24. 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
25. 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?
26. 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
27. 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