The original article links to the Kotlin coroutine manual.

I recently took some time out to translate three of kotlinx.coroutines’ official starting guides and put them on my GitBook, which you can check out here. However, the documentation is quite extensive, and I’ll summarize it in order to clarify what makes coroutines special.

What is a coroutine

The definition of coroutines is not easy to describe, so LET me just give you a brief description of coroutines in terms of purpose and definition.

Lightweight threads

The title may not be accurate, but it gives you a sense of its utility. Coroutines work on threads. We know that threads are scheduled by the system (language system or operating system) and there is some overhead in switching. And coroutine, its switch by the program itself to control, both CPU consumption and memory consumption are greatly reduced.

From this point on, its application scenarios may lie in improving hardware performance bottlenecks. For example, you can start 100,000 coroutines without any problem, but what if you start 100,000 threads?

A pauseable program

That’s the nature of coroutines compared to the first point; It is also from this point that coroutines play a big role. In coroutines, one piece of code can be paused to execute another. Suspended code can also be resumed at any time under your control.

This is a great use in front-end programming — to avoid callback hell. In terms of Android programming, before Rx, it was standard to define an interface to receive the result of an asynchronous operation using a callback. Rx, with its clever transformations, solves the problem of callback hell with reactive code and a chain of callbacks (supplemented by lambda expressions that make it look like there are no callbacks). But here, those of you who are used to writing code in imperative style need to understand a little bit about functional programming. Unlike coroutines, the code can be paused! That is, when I get the data asynchronously through the getUser() method, the code block that calls it can choose to suspend until it gets the data and then resume running. The code looks like this:

val user = getUser() // getUser is suspend function
Copy the code

Does it look the same as synchronous code?

For those of you who have written JS, it looks familiar:

async function getUser() {
    try {
        const response = await fetchUser();
        // ...
    } catch (e) {
        // error handle}}Copy the code

Yes, with coroutines, Kotlin can write similar code!

Use of coroutines

The skeleton of a coroutine

First, you need to start the coroutine through a constructor. There are two official infrastructure constructs currently available:

  • launch
  • runBlocking

They both start a coroutine, the difference being that the former does not block the current thread and returns a reference to the coroutine, while the latter waits for the code execution of the coroutine to finish before executing the rest of the code.

Second, Kotlin adds a new keyword to coroutines: suspend. Functions/methods/code blocks decorated by this keyword can only be called by coroutine code (that is, inside the block arguments of the constructor above) or by functions/methods/code blocks decorated by suspend. To put it simply, suspend fun can only be called suspend FUN (the type declaration for the last argument to the coroutine constructor is suspend CoroutineScope.() -> Unit).

With these two things in mind, you can write the simplest coroutine code:

fun main(args: Array<String>) {
    repeat(100_000) { // Start 100,000 coroutines
        launch { suspendPrint() }
    }
    Thread.sleep(1200) // Wait for the end of coroutine code
}

suspend fun suspendPrint(a) {
    delay(1000)
    println(".")}Copy the code

Delay is suspend fun.

In addition to the above two points, another very important concept is context. Although coroutines are thread dependent, a coroutine is not tied to a thread. You can specify the context when you start the coroutine, and you can switch the context within the coroutine withContext. This context, an object of the CoroutineDispatcher class, is, as the name suggests, used to perform coroutine scheduling. For example, if you need to create a new thread to run the coroutine code, you can do this:

launch(context = newSingleThreadContext("new-thread")) { delay(1000)}Copy the code

The above three points are important for me. Of course, there are also the cancellation of coroutines, the life cycle of coroutines, the relationship between coroutines and subcoroutines, etc. These points can be found in the official documents or my translation.

Normal operation

Async and await

As far as I know, async and await are the two keywords of JS and C#, simplifying asynchronous operations (of course, the details of the two languages are different). But in Kotlin, async is actually a normal function:

fun main(args: Array<String>) = runBlocking<Unit> {
    val result: Deferred<String> = async { doSomethingTimeout() }
	println("I will got the result ${result.await()}")
}

suspend fun doSomethingTimeout(a): String {
    delay(1000)
    return "Result"
}
Copy the code

In this case, the async block executes immediately after a new coroutine is started and returns a value of type Deferred. The await method is called and the current coroutine is suspended until the async block execution result is obtained.

Retrofit is an excellent solution for RESTful architecture, for which someone has adapted adapter for coroutines. I know of two things:

  • Kotlin Coroutines for Retrofit is feature-rich, has exception handling, and is well documented;
  • Kotlin Coroutine (Experimental) Adapter by Jake Wharton.

The former is not a Retrofit Adapter; Andrey Mischenko simply adds an extension function to the Call class. But they both use Deferred objects to process the results.

The channel correlation

There is the concept of a channel, which, as its name implies, sends and receives events. The simplest use is to call its send and receive methods. Note, however, that these two methods wait for each other, so they must run on different coroutines.

fun main(args: Array<String>) = runBlocking<Unit> {
    val channel = Channel()
    launch {
        for (x in 1.. 5) channel.send(x)
        channel.close()
    }
    for (x in 1.. 5) println(channel.receive())
    // or `for (x in channel) println(x)`
}
Copy the code

As you can see above, a channel itself can be iterated, and the end condition of the iteration is channel.close().

A customonClickmethods

The official documentation provides a channel version of the onClick method implementation, I think it is better:

fun View.onClick(action: suspend (View) -> Unit) {
    val eventActor = actor<View>(UI) {
        for (event in channel) action(event)
    }
    setOnClickListener {
        eventActor.offer(it)
    }
}
Copy the code

The actor here has a channel inside that receives data from the outside. When the click event is generated, the actor sends the data to it, and the channel iteration moves forward, invoking the incoming action. You can also deal with the back pressure problem with parameters here.

From this application, we can extend the idea that any operation triggered by an event can be implemented in a similar way. Of course, whether implemented in a good or bad way.

Custom Rx operators

So far, coroutines and Rx don’t seem to coexist, and their functions are mostly repetitive, leading to many scenarios that are either/or. Through the official third manual, however, I found that coroutines also had a module written specifically for Rx, allowing us to write Rx code as coroutines. The publish function is a bridge between the two:

fun range(context: CoroutineContext, start: Int, count: Int): Publisher<Int> = 
	publish<Int>(context) {
		for (x in start until start + count) send(x)
	}
Copy the code

Publish can channel code internally and send data to the next level, which returns Publisher as in the Rx standard. ConsumeEach can be extended to receive each item of data.

range(CommonPool, 1.5).consumeEach { println(it) }
Copy the code

The last

In a few days, I translated three guides and realized the gap between reading and writing. This article is intended to be a bullet point, many of the details are not covered, and more detailed content is still required for documentation. Of course, you can also join kotlinlang’s Coroutine Channel for discussion.