Kotlin is a language that provides only the most basic underlying API in the standard library so that various other libraries can take advantage of coroutines. Unlike many other languages with similar capabilities,async and await are not keywords or even part of the standard library in Kotlin. In addition,Kotlin’s concept of suspended functions provides a safer and less error-prone abstraction for asynchronous operations than futures and promises.

【 释 】 to make a promise, promise, promise, promise I hope you promise me. Give a person with… Kotlinx. coroutines is a features-rich coroutine library developed by JetBrains. It contains many of the high-level coroutine enabling primitives covered in this guide, including launch, Async, and so on. This article is a guide to the core features of Kotlinx. coroutines, with a series of examples, divided into different topics. To use coroutines and to walk through the examples in this guide, you need to add a dependency on the Kotlinx-Coroutines-core module, as described in the README file in the project.

Coroutines basis

In essence, coroutines are lightweight threads. They start with the launch coroutine builder in some CoroutineScope contexts:

Import kotlinx.coroutines.* class CoroutineTest {fun main() {globalscope.launch {// Start a new coroutine in the background and continue delay(1000L) // Println ("CoroutineTest World! Thread.sleep(8000L) // block the main Thread for 2 seconds to keep the JVM alive}}Copy the code

Bridge blocking and non-blocking worlds

The first example mixes non-blocking delay(……) in the same code Sleep with blocked thread.sleep (……) . This makes it easy to remember which is blocking and which is non-blocking. Let’s explicitly use the runBlocking coroutine builder to block:

Import kotlinx.coroutines.* fun main() = runBlocking<Unit> {// Start executing the main coroutine Globalscope.launch {// Start a new coroutine in the background and continue delay(1000L) println("World!" )} println("Hello,") // The main coroutine immediately executes delay(2000L) // delay 2 seconds to keep the JVM alive}Copy the code

The results are similar, but the code only uses the non-blocking function delay. The main thread that called runBlocking will block until the coroutine inside runBlocking completes. This example can be rewritten in a more idiomatic way, using runBlocking to wrap the execution of the main function:

Import kotlinx.coroutines.* fun main() = runBlocking<Unit> {// Start executing the main coroutine Globalscope.launch {// Start a new coroutine in the background and continue delay(1000L) println("World!" )} println("Hello,") // The main coroutine immediately executes delay(2000L) // delay 2 seconds to keep the JVM alive}Copy the code

Here runBlocking {…… } as the adapter used to start the top-level main coroutine. We explicitly specify its return type Unit because in Kotlin main must return Unit.

This is also a way of writing unit tests for pending functions:

annotation class Test class MyTest { @Test fun testMySuspendingFunction() = runBlocking<Unit> { // Here we can use any assertion style we like to use the suspend function}}Copy the code

Waiting for a job

A delay waiting for another coroutine to run is not a good option. Let’s wait explicitly (without blocking) for the execution of the started background Job to finish:

Fun main4() = runBlocking {val job = globalscope.launch {// Start a new coroutine and keep a reference to the job delay(1000L) println("World!" } println("Hello,") job.join() // wait until subcoroutine execution ends}Copy the code

Now, the result is still the same, but the main coroutine has nothing to do with the duration of the background job. Much better.

The practical use of coroutines leaves some room for improvement. When we use GlobalScope.Launch, we create a top-level coroutine. Although it is lightweight, it still consumes some memory resources when it runs. If we forget to keep a reference to the newly started coroutine, it will continue to run. What if the code in the coroutine hangs (for example, we mistakenly delay it too long), what if we start too many coroutines and run out of memory? All started coroutines must be manually referenced and joined, which is error-prone. There is a better solution. We can use structured concurrency in our code. We can start the coroutine in the specified scope where the operation is performed, rather than in a GlobalScope as we normally do with threads (which are always global). In our example, we use the runBlocking coroutine builder to convert the main function into a coroutine. Each coroutine builder, including runBlocking, adds an instance of CoroutineScope to the scope in which its code block resides. We can start coroutines in this scope without explicitly joining it, because the external coroutine (runBlocking in the example) does not end until all coroutines started in its scope have executed. Therefore, we can simplify our example to:

Fun main() = runBlocking {// this: CoroutineScope launch {// Start a new coroutine in the runBlocking scope delay(1000L) println("World!" ) } println("Hello,") }Copy the code

Scope builder

In addition to having coroutine scopes provided by different builders, you can declare your own scopes using the coroutineScope builder. It creates a coroutine scope and does not end until all initiated subroutines have executed. RunBlocking and coroutineScope may look similar because they both wait for their coroutine body and all its child coroutines to end. The main difference between the two is that the runBlocking method blocks the current thread to wait, while the coroutineScope simply hangs, freeing the underlying thread for other purposes. Because of this difference,runBlocking is a normal function and coroutineScope is a suspend function. This can be demonstrated by the following example:

fun main() = runBlocking { // this: CoroutineScope launch {delay(200L) println("Task from runBlocking")} CoroutineScope { Delay (500L) println("Task from nested launch")} delay(100L) println("Task from coroutine scope") // This line will be output before the embedded launch } println("Coroutine scope is over")Copy the code

Note that when waiting for the embedded launch, immediately after the “Task from Coroutine Scope” message, “Task from runBlocking” is executed and printed even though the coroutineScope has not yet finished.

Extract function refactoring

Let’s launch {…… } internal code blocks are extracted into separate functions. When you perform an “extract function” refactoring on this code, you get a new function with the suspend modifier. That’s your first suspend function. A suspend function can be used inside a coroutine just like a normal function, but with the added feature that other suspend functions (such as delay in this case) can also be used to suspend execution of the coroutine.

Fun main() = runBlocking {launch {doWorld()} println("Hello,")} suspend fun doWorld() {delay(1000L) println("World!" )}Copy the code

But what if the extracted function contains a coroutine builder that is called in the current scope? In this case, only the suspend modifier on the extracted function is not sufficient. Writing a doWorld extension method for CoroutineScope is one solution, but this may not always be appropriate, as it does not make the API any clearer. The usual solution is to either explicitly refer to CoroutineScope as a field in the class that contains the function, or implicitly when an external class implements CoroutineScope. As a last resort, you can use the CoroutineScope(Coroutine Context), but this method is structurally unsafe because you can no longer control the scope in which the method executes. Only private apis can use this builder

Coroutines are very lightweight

fun main7() = runBlocking { ///val i : Long =0; Repeat (100_000) {// Launch numerous coroutines launch {delay(1000L) println(".")}}Copy the code

It starts 100,000 coroutines, and after a second, each coroutine outputs a point. Now, try using threads. What happens? (It is likely that your code will produce some sort of out-of-memory error.)

Global coroutines are like daemon threads

The following code starts a long-running coroutine in GlobalScope that prints “I’m sleeping” twice per second and then returns after a delay in the main function.

fun main() = runBlocking { GlobalScope.launch { repeat(1000) { i -> println("I'm sleeping $i ..." ) delay(500L) } } delay(1300L) // just quit after delay }Copy the code

You can run the program and see it output the following three lines before terminating:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...

Copy the code

An active coroutine started in GlobalScope does not keep the process alive. They are like daemon threads.