Experimenting with Kotlin Coroutines

As I mentioned previously, I've started on a new project that uses Kotlin heavily. I don't have extensive experience with Kotlin yet, but I'm learning a lot and love it. I recently needed to submit multiple HTTP requests concurrently. There were a few examples in the codebase that I had come across, but I never had the need to dig deeper. The existing code used Kotlin Coroutines. I'm familiar with Threads in Java and concurrent programming—I've even done a side project using Ratpack—but Kotlin Coroutines were fairly new to me.

Real quick, for those that don't know, Kotlin Coroutines allow one to create asynchronous programs in a fluent way; they can be thought of as light-weight threads. The existing code looked something like this (simplified for clarity and to protect the innocent):

import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope

suspend fun fetch(): MyClass =
    coroutineScope {
        val data1 = async { fetchData1() }
        val data2 = async { fetchData2() }
        MyClass(data1.await(), data2.await())

What I needed was something similar, but my use case didn't require returning a result. I needed to fetch a result containing a list of values, and submit a request for each item concurrently without waiting for a result, but wait for all requests to finish. I searched the internet for simple examples, but almost everything was too long or confusing. And in my opinion, the best way to learn something new is to just play around with it.

So I created a simple Kotlin Scratch file in IntelliJ Idea to experiment. I'm including a screenshot below so you can see the output side-by-side, which should make it easier to understand. See this gist if you want to copy the code and play around with it in Idea. Note this will require you add the necessary kotlinx.coroutine dependencies and make them available on your scratch file's classpath. Or if you want to play around with it online, I also copied the code to Kotlin's Playground.

Screen Shot 2022-04-20 at 11.15.11 AM.png

A few things I learned about Kotlin Coroutines:

  1. Use launch when you don't need to return a result. Also, launch returns an instance of Job which allows you to call join if you do need to wait till it completes. Or use something like listOf(job1, job2).joinAll() if you need to explicitly wait for all jobs to complete.
  2. Use async when you do need to return a result. It returns an instance of Deferred, which extends Job. You can call the await function when you need to wait till it completes.
  3. Calling launch/async will start that coroutine concurrently immediately; no need to call a start method or anything, like you have to do with a Java Thread.
  4. All the jobs will be complete by the time the program exits coroutineScope or runBlocking. This was one of my biggest questions: did I have to explicitly call join/await to ensure all jobs were complete before returning from my function? And the answer is no. For example, notice in my scratch's output the finished statement is last and isn't emitted until all the jobs are complete. This makes sense since I am calling runBlocking, but I assume the same is true when using coroutineScope and that aligns with the Kotlin docs for coroutineScope (which to me were initially confusing).

Anyways, I feel a little more knowledgeable about Kotlin Coroutines. Still a ton more to learn, but that will come with time. Hope it helps someone else.