Kotlin Coroutines not complicated, I'll help you manage a reason

Coroutines coroutine

In summary Kotlin something recently, and I found coroutine this is not easy to say. Before the essay on poorly written, so I decided to rewrite.
Repeatedly studied various documents and official website tutorial blog, In this part is the most the foundation is also the most important content, and strive to white also can read and understand.

Coroutines concept

Coroutines (coroutines), a computer program components, by allowing the suspend and resume task execution, to support non-preemptive multitasking. (See Wiki ).

Coroutine mainly to asynchronous, non-blocking code. This concept is not unique to Kotlin, multiple language Go, Python, etc. are supported.

Kotlin Coroutines

Kotlin Coroutine to do with asynchronous and non-blocking tasks, the main advantage is good code readability, not the callback function. (Using coroutines to write asynchronous code at first glance like a synchronization code.)

Kotlin support for coroutines in the language level, the standard library provides only minimal APIs, and then many features are proxy to the library.

Kotlin only added suspendas a keyword.
asyncAnd awaitnot Kotlin keywords, not part of the standard library.

Compared futures and promises, kotlin in suspending functionconcept provides a more secure and less error-prone abstraction for asynchronous operation.

kotlinx.coroutinesIs coroutine library, in order to use its core functions, projects need to increase kotlinx-coroutines-corereliance.

Coroutines Basics: coroutines in the end what is?

The first section of the official demo:

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch


fun main() {
    GlobalScope.launch { // launch a new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

Output of this code:
prints Hello, after a delay of 1s, print World.

The interpretation of this code:

launchA calculation started, this calculation is suspended (the Suspendable), which in the calculation process, the release of the bottom thread, when the coroutine execution is complete, resumes (resume).

This calculation can be suspended is called a coroutine (coroutine). So we can simply say that launchthe beginning of a new coroutine.

Note that the main thread needs to wait for the end of coroutines, comment out the last line Thread.sleep(2000L), only printing Hello, no World.

Relations Institute of processes and threads

coroutine (coroutines) can be understood as lightweight threads. more coroutines can run in parallel, waiting for each other, communicate with each other. The biggest difference between coroutines and threads coroutine is very lightweight (cheap), we can create thousands tens of thousands of coroutine regardless of performance.

Coroutine is running on a thread can be suspended operations can be suspended, meaning that operations can be suspended, removed from the thread, stored in the memory. In this case, the thread would be free to do other things. When calculating preparations Hershey continues, it will return the thread (but do not necessarily have the same thread).

By default, coroutines run in a shared thread pool, the thread is still there, only one thread can run multiple coroutines, so not too many threads as necessary.

debugging

Plus the name of the thread in the above code:

fun main() {
    GlobalScope.launch {
        // launch a new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World! + ${Thread.currentThread().name}") // print after delay
    }
    println("Hello, + ${Thread.currentThread().name}") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

VM options may be provided in the Edit Configurations IDE: -Dkotlinx.coroutines.debugRun the program prints the coroutine information code running in the log:

Hello, + main
World! + DefaultDispatcher-worker-1 @coroutine#1

suspend function

In the above example delaythe method is a suspend function.
delay()And the Thread.sleep()difference is: delay(). Coroutine method may be delayed without blocking thread (It does not block a thread, but only suspends the coroutine itself) and Thread.sleep()the current thread blocked.

So, suspend mean that the scope coroutine is suspended, but the current thread code outside the scope coroutine not be blocked.

If GlobalScope.launchreplaced thread, delay method will appear below the red line error:

Suspend functions are only allowed to be called from a coroutine or another suspend function

The method can only be called suspend coroutine suspend or another method.

During the coroutine waiting, the thread will return to the thread pool, when the end of the coroutine wait, coroutine will be restored on a free thread pool. (The thread is returned to the pool while the coroutine is waiting, and when the waiting is done, the coroutine resumes on a free thread in the pool.)

Start coroutine

Start a new coroutine, used mainly in the following ways:

  • launch
  • async
  • runBlocking

They are called coroutine buildersDifferent libraries can be defined much more are built.

runBlocking: blocking and non-blocking connections in the world

runBlockingBlocking and non-blocking used to connect the world.

runBlockingCan create a coroutine blocks the current thread, so it is mainly used in the main function or used in the test, as the link function.

Examples of the foregoing example can be rewritten as:

fun main() = runBlocking<Unit> {
    // start main coroutine
    GlobalScope.launch {
        // launch a new coroutine in background and continue
        delay(1000L)
        println("World! + ${Thread.currentThread().name}")
    }
    println("Hello, + ${Thread.currentThread().name}") // main coroutine continues here immediately
    delay(2000L) // delaying for 2 seconds to keep JVM alive
}

Finally no longer in use Thread.sleep(), use delay()it.
Procedures output:

Hello, + main @coroutine#1
World! + DefaultDispatcher-worker-1 @coroutine#2

launch: return to the Job

Examples of the above delay time period to wait for the end of a coroutine, is not a good method.

launchReturn Jobon behalf of a coroutine, we can use Jobthe join()method to explicitly wait for the end of this coroutine:

fun main() = runBlocking {
    val job = GlobalScope.launch {
        // launch a new coroutine and keep a reference to its Job
        delay(1000L)
        println("World! + ${Thread.currentThread().name}")
    }
    println("Hello, + ${Thread.currentThread().name}")
    job.join() // wait until child coroutine completes
}

The output is the same as above.

JobAnother important use is cancel()for canceling a task coroutine no longer needed.

async: Returns the value from the coroutine

asyncOpen thread, return Deferred<T>, Deferred<T>is Joba sub-class, there is a await()function that returns the result of the coroutine.

await()Also suspend function can only be called within the coroutine.

fun main() = runBlocking {
    // @coroutine#1
    println(Thread.currentThread().name)
    val deferred: Deferred<Int> = async {
        // @coroutine#2
        loadData()
    }
    println("waiting..." + Thread.currentThread().name)
    println(deferred.await()) // suspend @coroutine#1
}

suspend fun loadData(): Int {
    println("loading..." + Thread.currentThread().name)
    delay(1000L) // suspend @coroutine#2
    println("loaded!" + Thread.currentThread().name)
    return 42
}

operation result:

main @coroutine#1
waiting...main @coroutine#1
loading...main @coroutine#2
loaded!main @coroutine#2
42

Context, Dispatcher和Scope

Look at the launchmethod declaration:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
...
}

Several of the concepts we have to find out.

Coroutine always in a context Run, type is an interface CoroutineContext. Coroutine context is a set of indexes, which contains a variety of elements, there is an important element Joband dispatcher. JobOn behalf of the Association process, then the dispatcher is to do what?

Builder build coroutines the coroutine: launch, async, are CoroutineScopethe type of extension methods to view CoroutineScopethe interface, which contains CoroutineContexta reference to what scope is there any role in it.??

Here we have to answer these questions.

Dispatchers and thread

The Context CoroutineDispatchercan specify what coroutine running thread can be assigned a thread, the thread pool, or not.

Look at an example:

fun main() = runBlocking<Unit> {
    launch {
        // context of the parent, main runBlocking coroutine
        println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Unconfined) {
        // not confined -- will work with main thread
        println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(Dispatchers.Default) {
        // will get dispatched to DefaultDispatcher
        println("Default               : I'm working in thread ${Thread.currentThread().name}")
    }
    launch(newSingleThreadContext("MyOwnThread")) {
        // will get its own new thread
        println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
    }
}

After running print out:

Unconfined            : I'm working in thread main
Default               : I'm working in thread DefaultDispatcher-worker-1
newSingleThreadContext: I'm working in thread MyOwnThread
main runBlocking      : I'm working in thread main

API offers several options:

  • Dispatchers.DefaultUse shared thread pool on behalf of the JVM, whose size is determined by the number of CPU cores, but even single-core also has two threads. Typically used for CPU-intensive work, such as sorting or complex calculations.
  • Dispatchers.MainSpecifies the main thread, used to make UI update-related things. (Need to add a dependent, such as kotlinx-coroutines-android.) If we are on the main thread start a new coroutine, the main thread is busy, the coroutine will be suspended only if It will be free to resume execution thread.
  • Dispatchers.IO: The thread pool on-demand creation of a network or work to read and write files.
  • Dispatchers.Unconfined: You do not specify a particular thread, which is a special dispatcher.

If you do not explicitly specify the dispatcher, coroutine will inherit the scope of the context it is activated (which contains the dispatcher).

In practice, it is recommended to use an external dispatcher scope of the decision by the context of the caller. This also facilitate testing.

newSingleThreadContextCreate a thread to run coroutine, a dedicated thread is regarded as an expensive resource, to be released in practical applications or storage reuse.

Switching threads can also be used withContext, you can run code under specified coroutine context, it is suspended until the end result is returned.
Another way is to start a new coroutine, then joinwait explicitly suspended.

Android UI in this application, the more common practice is to use top coroutine CoroutineDispatchers.Main, when the need to do something in the other thread time, and then explicitly specify a different dispatcher.

Scope is what?

When launch, asyncor runBlockingopen a new coroutine time, they automatically creates a corresponding scope. All of these methods have a receiver with lambda parameter default receiver type CoroutineScope.

IDE will prompt this: CoroutineScope:

launch { /* this: CoroutineScope */
}

When we runBlocking, launchor asyncbraces which then creates a new coroutine time, it is automatically created in this scope:

fun main() = runBlocking {
    /* this: CoroutineScope */
    launch { /* ... */ }
    // the same as:
    this.launch { /* ... */ }
}

Since launchan extension method, the above example is the default receiver this.
In this example launchare started is called an external coroutine coroutine ( runBlockingstart coroutine) of the child. This relationship "parent-child" by passing scope : child's start in the scope parent.

Parent-child relationship coroutines:

  • When a coroutine is started in the scope of another coroutine, automatically inherit its context, and new Job coroutine will act as a child of the parent Job coroutine.

So, with regard to scope There are two key knowledge points:

  • We open a coroutine, always in a CoroutineScopeLane.
  • Scope for managing parent-child relationships and structures among different coroutine.

Parent-child relationship coroutine has the following two characteristics:

  • The parent coroutine is canceled, all the sub-coroutine are canceled.
  • Father coroutine always be waiting for the end of all the sub-coroutine.

It is noteworthy that, you can not start the coroutine to create a new scope can be used to create scope factory method: MainScope()or CoroutineScope().

coroutineScope()The method can also create scope. When we needed a structured way to start a new coroutine inside suspend function, we created a new scope, automatically become child outside the scope of the suspend function is called.

Parent-child relationship above, may be further abstracted to no parent coroutine, the scope of which manages all of the sub coroutine.

Scope solve any problems in the practical application of it? If our application, there is an object that has its own life cycle, but the objects are not co-routines, such as Android applications Activity, which started to do some coroutine asynchronous operations, update data, etc., is destroyed when the activity of the need to cancel all the coroutine to avoid memory leaks we can use. CoroutineScopeto do it: create a CoroutineScopetarget binding and activity of the life cycle, or allow activity to achieve CoroutineScopeinterface.

Therefore, the main scope of the role is to record all the coroutine, and can cancel them.

A CoroutineScope keeps track of all your coroutines, and it can cancel all of the coroutines started in it.

Structured Concurrency

Such a mechanism would use scope Association organized a structured process, known as "structured concurrency".
Benefits are:

  • scope automatically responsible for child coroutine sub coroutine life and scope bindings.
  • scope automatically cancel all sub coroutine.
  • scope automatically wait for the end of all the sub-coroutine. If a parent co-Cheng scope and binding, parent coroutine will wait for the scope of all the sub-association process is complete.

With this concurrent mode structured: when we can create top-level association process, designate a primary context once, all nested coroutine will automatically inherit this context, we can only make changes when necessary.

GlobalScope: daemon

GlobalScopeStart coroutine are independent, their life only by the application of restrictions. That GlobalScopestarted the coroutine no parent, is located outside of the scope when it is activated and it does not matter.

launch(Dispatchers.Default) { ... }And GlobalScope.launch { ... }dispatcher use is the same.

GlobalScopeStart coroutine process and not remain active. They are like daemon threads (daemon thread), as if the JVM general found no other thread will be closed.

reference

Third-party blog:

Guess you like

Origin www.cnblogs.com/mengdd/p/kotlin-coroutines-basics.html