This post is inspired by Dávid Karnok’s RxJava vs. Kotlin Coroutines, a quick look post. Let’s take a deeper look at how it is easy to build higher-order abstractions using Kotlin higher-order functions and coroutines.

Problem

David had stated the following problem:

Let’s say we have two functions imitating unreliable service: f1 and f2, both returning a number after some delay. We have to call these services, sum up their returned values and present it to the user. However, if this doesn’t happen within 500 milliseconds, we don’t expect it to happen reasonably faster, thus we’d like to cancel and retry the two services for a limited amount of time before giving up after some number of retries.

The following functions imitate those services and their unreliable nature. The first parameter to those functions is the attempt number (starting from one).

suspend fun f1(i: Int): Int {

println("f1 attempt $i")

delay(if (i != 3) 2000 else 200)

return 1

}



suspend fun f2(i: Int): Int {

println("f2 attempt $i")

delay(if (i != 3) 2000 else 200)

return 2

}

In practice we are doing some asynchronous networking operation in f1 and f2. We imitate asynchronous operation with delay that suspends execution for a specified number of milliseconds. Those functions are fast enough only on the third attempt.

Both f1 and f2 are cancellable by the virtue of delay being cancellable, just like a real-life asynchronous network operation is going to be, which is important for a solution that is presented below.

Solution

Let us embrace functional programming and solve this problem via composition of higher-order functions. One such functional building block is already provided by kotlinx.coroutines library as withTimeout higher-order function. We use it to implement our own higher-order function that encapsulates the desired retry logic:

suspend fun <T> retry(block: suspend (Int) -> T): T {

for (i in 1..5) { // try 5 times

try {

return withTimeout(500) { // with timeout

block(i)

}

} catch (e: TimeoutCancellationException) { /* retry */ }

}

return block(0) // last time just invoke without timeout

}

It is easy to understand the above code, because it directly represents the retry logic that we would like to achieve, yet it is not tied to a particular function, so we can now do both retry { f1(it) } and retry { f2(it) } .

Both retry function and its parameter block are marked with suspend modifier. That is the only difference between this code and the code that we might have written if the problem would have been stated for synchronous, blocking functions f1 and f2 instead. That is the chief value proposition of Kotlin Coroutines — you don’t have to learn the whole new language to compose your asynchronous computations. You can reuse approaches and solutions from the blocking world.

We want to execute both f1 and f2 concurrently. The guiding principle of kotlinx.coroutines design is that concurrency must be explicit, so there is a separate higher-order function named async for cases like that.

async { ... } starts asynchronous computation and continues to execute next statement concurrently, returning a deferred value that we can await later to get the result of computation we’ve started.

fun main(args: Array<String>) = runBlocking {

val time = measureTimeMillis {

val v1 = async { retry { f1(it) } }

val v2 = async { retry { f2(it) } }

println("Result = ${v1.await() + v2.await()}")

}

println("Completed in $time ms")

}

Running the above code produces the expected result of 3 and expected execution time of around 1200 ms (two timeouts of 500 ms each plus the third, successful execution in 200 ms):

f2 attempt 1

f1 attempt 1

f2 attempt 2

f1 attempt 2

f2 attempt 3

f1 attempt 3

Result = 3

Completed in 1234 ms

The whole code is available in this gist.

Further reading

If you are new to the world of Kotlin Coroutines and find some of the concepts presented herein new or confusing, you should consider watching Introduction to Coroutines talk from KotlinConf 2017 and reading The Guide to kotlinx.coroutines by example.