What is the principal difference between kotlin coroutines and Java 8 CompletableFuture?


#1

I am reading documentation on coroutines and trying to understand the concept. Also I have some experience using Java 8 CompletableFuture class and its infrastructure. My question is the following: what is the principal advantage of coroutines in comparison with CompletableFuture? I am asking not to prove something, but to understand this new feature better.

So far I can see two important things in coroutines which are not present in CompletableFuture:

  • Automatic cancellation of children jobs. It is definitely a very good thing, but it could be emulated by rather simple extension over CompletableFuture.
  • Direct control over context. For now I am not sure, how it could be used on practice.

Is there something else I am missing?


#2

You can use coroutines with CompletableFuture

and you can use Job without coroutine but the main advantage of coroutines is the syntax, this avoid easily the callback-hell.

Try yourself to write the above example using vanilla CompletableFuture without block the thread.


#3

This example is still pretty easy to solve:

fun combineImagesAsync(name1: String, name2: String): CompletableFuture<Image>{
    val future1 = CompletableFuture.supplyAsync(name1-> ...) // start loading first image
    val future2 = CompletableFuture.supplyAsync(name2-> ...) // start loading second image
    return future1.thenCombine(future2){i1,i2 -> ...}
}

Alternatively instead of last line one can do that:

return CompletableFuture.allOf(future1,future2).thenApply{
  val res1 = future1.get()
  val res2 = future.get()
  ...
}

It looks worse of course, but question is: is there some fundamental difference here?


#4

Kotlin coroutines do not neccesarily causes thread context switches. I can use them to write generator routines that extend or filter an input list to an output list of unknown size, were I do not need threads. Map and forEach loops are not suitable for this, but yield() is very powerful for that.

For instance, enumerating and flattening a tree structure, such as a directory structure, to a flat list of leaf nodes (files).

I’ve used this often in Python and am happy to now have this power in Kotlin too, in an easy to build way.


#5

Maybe there is a little misunderstanding: Kotlin in a language, so the important part of it is the syntax.

CompletableFuture and Job are these library’s classes and both are quite similar,

Coroutines allow to write suspendable method and work nicely with CompletableFuture, Job and so on.

OT

Some time ago I wrote an asynchronous implementation of recursive Quick Sort: https://github.com/fvasco/jug-2017-07/blob/master/kotlin1.1/src/main/kotlin/Main.kt#L23
The first consideration is that it is pretty small and readable.
The second one is that it can sort a big list without throw a StackOverflowError.
In such case coroutines allows iteration over recursion, again: the main difference is the syntax.


#6

While control flow is linear the advantages of coroutines may be not so clearly obvious. However things change when loops, conditions, try-catch blocks arise.
Coroutines make it easy to write complex control flows involving suspension points.

For example this code that executes suspend function several times until it completes successfully looks pretty straightforward:

suspend fun downloadImageWithRetry(retryCount: Int): Image {
    repeat(retryCount - 1) { 
       try {
           return downloadImage()
       }
       catch (e: IOException) {
          // ignore or log failed trial
       }
    }
    return downloadImage() // last one trial
}

suspend fun downloadImage(): Image = ... // download without retries

While it is possible to rewrite this control flow such that suspend fun(): Image becomes fun (): CompletableFuture<Image>, the result that requires to rewrite a loop to a recursion is not so easy to compose and even more not so easy to grasp later:

fun downloadImage(): CompletableFuture<Image> = TODO()

fun downloadImageWithRetries(retryCount: Int): CompletableFuture<Image> {
    return downloadImage().handle { image: Image?, error: Throwable? ->
        if (error != null) {
            if (error is IOException && retryCount > 1)
                // ignore or log
                downloadImageWithRetries(retryCount - 1)
            else
                throw error
        }
        else {
            CompletableFuture.completedFuture(image!!)
        }
    }.thenCompose { it }
}

Right way to use coroutines
#7

Thanks Ilya, I thin I am starting to understand how to use it properly, but I will need some time to play around to get the goof feeling.

In my project I am using CopletableFuture quite extensively to implement lazy calculations with dependencies and indeed there are some cases when control flow becomes complicated. I hide all of that under the library hood, so these problems are not seen to the user. Maybe it would be better to expose this part to the user via coroutines instead. Of course it would mean moving all of the codebase to kotlin from java.

Thanks also for all other replies.Some times it is not so simple to see some things when there are ones you are familiar with.


#8

OK, I finally tried it myself. It took some time to figure out some minor details, but after that… guys, it is amazing. The code size reduction is about 5-10 times, and readability is dramatically increased. Also I do not have to move my code to Kotlin at all since there are convenience conversion functions between coroutines and CompletableFuture. I just implemented kotlin version on top of my own Java 8 interfaces.


#9

If your project is open source, we’d appreciate if you share results of your work or, at least, blog about your experience (even if you cannot share the code).


#10

My project is open source, but it is in early stage of development and will remain so for nearest future since it is just huge and I don’t have funding for it. I am working on scientific software and most of the core code is written in Java. I have only small experimental kotlin part called kodex, but I plan to expand it.

I also try to write new smaller projects in kotlin. For example this one (it is a simulation tool for cosmic muons in the deep underground laboratory). Also I am trying to teach students at MIPT good practices in scientific programming using kotlin (with examples).

I will try to think of something in terms of post about kotlin usage for scientific programs, but it will not be too soon because it takes time and I currently do not have it.


#11

Here’s a code example of how flattening of a tree structure with Kotlin coroutines exactly mirrors Python coroutines, and how it makes lazy tree walking possible by consuming the Kotlin code from Java. (The article is a bit lengthy, as all that was new to me at the time, but it’s still nice enough to see, I think, especially when you come from Java. Nothing new for Python people…)

– Sebastian


#12

Thanks for the article.

I’m familiar with how to do this kind of things in Python and have been doing the same in Kotlin 1.1 wherever applicable - in fact, twice today :wink: