Basic key behaviors of Kotlin coroutines
Tags: kotlin, coroutine
Published: 2020-04-30 20:25:09

When I first learned about Kotlin coroutines, I was having a hard time identifying the specific behavior of all keywords: `launch`, `withContext`, `async`, etc...  

Is my coroutine suspended ? How can I wait many of them at one point ? Where are they executing ?  

Quickly, I decided to sum up some key basic behaviors to help me with that, and that's what I will share today ! This post is aimed at developers who are not using coroutines right now, or are using it but need a quick refresh on some basic behaviors.

### What is a coroutine ?

According to the [offical documentation](https://kotlinlang.org/docs/reference/coroutines/basics.html), a coroutine is essentially a ([very](https://youtu.be/_hfBv0a09Jc?t=1840)) light-weight thread.
It enables asynchronous operations without the need of callbacks, `Promise`, or `Future` concepts. Write imperative code, run concurrent operations.

### Snippets setup

Let's declare some context for our coroutines snippets:

```kotlin
private val job = Job()
private val customScope = CoroutineScope(job + Dispatchers.Default)

private suspend fun fakeHardWork(tag: String): String {
    println(tag)
    delay(4000)
    return "I'm exhausted"
}
```

- A [`Job`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/) is responsible for holding a reference to a coroutine. With that reference you can `.start()` the coroutine if it was not started, wait for its completion with `.join()` or cancel it with `.cancel()`
- `customScope` is a [`CoroutineScope`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html) composed of the `job` and a [`CoroutineDispatcher`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/index.html) (and it can be [much more](https://medium.com/@elizarov/coroutine-context-and-scope-c8b255d59055)). Yes, I added a `CoroutineDispatcher` to a `Job`. That's because they both are a [`CoroutineContext`](https://proandroiddev.com/demystifying-coroutinecontext-1ce5b68407ad) and they can be merged together.
- `fakeHardWork()` is, as the name can suggest, a `suspend` function faking a 4 seconds task

I did not get into the details of these objects that are part of the coroutine's vocabulary. I encourage you to read the documentation about all of them, or  find some [posts](https://dev.to/touchlab/kotlin-coroutines-cheat-sheet-5872) describing them.

##### Quick look at `CoroutineDispatcher`

The `CoroutineDispatcher` is responsible for describing *where* the coroutine must be executed. Some dispatchers are provided by the `core` library with these properties : 
- [`Dispatchers.Default`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html) which serves for basic background task.
- [`Dispatchers.IO`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html) offers a thread pool optimized for IO tasks (only available for the JVM).
- [`Dispatchers.Unconfined`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-unconfined.html) if the thread of execution does not matter.

Finally, [`Dispatchers.Main`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-main.html) property is a special dispatcher that is allocated to **main thread** operations. Such tasks as manipulating the UI are often required to be executed on this dispatcher (Android crashes the app if that's not the case for example).

> Using the `Dispatchers.Main` property without providing a concrete implementation in the classpath of your application will throw an `IllegalStateException`. `kotlinx-coroutines-android` is the provider of the `Main` dispatcher for Android.

## Launching a coroutine

```kotlin
fun newCoroutine() = 
    customScope.launch { // starts a new coroutine 'C1'

        fakeHardWork("working") // this suspends C1 until the function completes
        println("done")
    }
```

Theoretical output:
```kotlin
00s:0000ms : working
04s:0000ms : done
```

This piece of code is quit straighforward. We launch a new coroutine inside the `customScope`. Since this scope has been initialized with `Dispatchers.Default`, it's executing on the corresponding thread pool.

> [`launch`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html) creates and executes a new coroutine and must be called on an existing `CoroutineScope`.

## Switching Dispatchers

```kotlin
fun switchDispatchers() = 
    customScope.launch { // starts a new coroutine 'C1'
        fakeHardWork("work from Dispatchers.Default") // same as above, suspends C1

        // switch Dispatcher, still suspends C1
        withContext(Dispatchers.IO) { 
            fakeHardWork("work from Dispatchers.IO") 
        }
        println("done on Dispatchers.Default")
    }
```

Theoretical output:  
```kotlin
00s:0000ms : work from Dispatchers.Default
04s:0000ms : work from Dispatchers.IO
08s:0000ms : done on Dispatchers.Default
```

Here, [`withContext`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html) changes the execution context of the block in parameter. The second call to `fakeHardWork()` is executed on Dispatchers.IO, and `withContext` is still suspending C1. 

`withContext` merges the current [`CoroutineContext`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/) it has been launched from (here `customScope`'s context), with the context you passed in parameter.

> `withContext` does not create a new coroutine and suspends its caller until its block is executed.

## Parallel execution inside a coroutine

```kotlin
fun parallelWorkInCoroutine() = 
    customScope.launch { // starts a new coroutine C1
        
        // fire a new coroutine C2. C1 is not suspended
        val work_1 = async { fakeHardWork("w_1 from Dispatchers.Default") }
        // fire a new coroutine C3 on another Dispatcher. C1 is not suspended
        val work_2 = async(Dispatchers.IO) { fakeHardWork("w_2 from Dispatchers.IO") }

        // await() calls suspend C1
        val result = work_1.await() + work_2.await()
        println(result)
    }
```
Theoretical output:
```kotlin
00s:0000ms : w_1 from Dispatchers.Default
00s:0010ms : w_2 from Dispatchers.IO
04s:0010ms : I'm exhaustedI'm exhausted
```

Now this is interesting. [`async`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) is a *coroutine builder*. Unlike `withContext`, it actually creates and starts a new coroutine while also *immediatly* returning a [`Deferred<T>`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html) (`T` being the return type of the block passed to `async`, a `String` in our example).  

We can then call [`await()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html) on this object to effectively suspend the caller coroutine (C1 in the snippet) and get back the `String` produced by the blocks passed to `async`.  

Here `result` equals to `"I'm exhaustedI'm exhausted"`.

With this, we could await many `Deffered` and then execute something only when all of them complete.

> `async` creates and starts a new coroutine. The returned `Deferred` allows us to suspend the caller coroutine and get the produced value.

## Wrapping it up

I described very few behaviors about coroutines, there's so much more to discover and to learn about them ! But I find these points to be good tools to start manipulating coroutines and learn step by step. 

I'll probably write later about other more advanced concepts. For now, if you were asking yourself if coroutines are worth a try, go ahead !

#### More starting resources 

If you are curious, [this great talk](https://www.youtube.com/watch?v=_hfBv0a09Jc) is a perfect introduction to Coroutines from the creator, Roman Elizarov.