Kotlin Coroutines, a deeper look

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).

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:

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.

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):

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.

Written by

Project Lead for the Kotlin Programming Language @JetBrains

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store