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 suspend
as a keyword.
async
And await
not Kotlin keywords, not part of the standard library.
Compared futures and promises, kotlin in suspending function
concept provides a more secure and less error-prone abstraction for asynchronous operation.
kotlinx.coroutines
Is coroutine library, in order to use its core functions, projects need to increase kotlinx-coroutines-core
reliance.
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:
launch
A 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 launch
the 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.debug
Run 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 delay
the 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.launch
replaced 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 builders
Different libraries can be defined much more are built.
runBlocking: blocking and non-blocking connections in the world
runBlocking
Blocking and non-blocking used to connect the world.
runBlocking
Can 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.
launch
Return Job
on behalf of a coroutine, we can use Job
the 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.
Job
Another important use is cancel()
for canceling a task coroutine no longer needed.
async: Returns the value from the coroutine
async
Open thread, return Deferred<T>
, Deferred<T>
is Job
a 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 launch
method 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 Job
and dispatcher. Job
On behalf of the Association process, then the dispatcher is to do what?
Builder build coroutines the coroutine: launch
, async
, are CoroutineScope
the type of extension methods to view CoroutineScope
the interface, which contains CoroutineContext
a reference to what scope is there any role in it.??
Here we have to answer these questions.
Dispatchers and thread
The Context CoroutineDispatcher
can 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.Default
Use 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.Main
Specifies the main thread, used to make UI update-related things. (Need to add a dependent, such askotlinx-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.
newSingleThreadContext
Create 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 join
wait 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
, async
or runBlocking
open 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
, launch
or async
braces 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 launch
an extension method, the above example is the default receiver this
.
In this example launch
are started is called an external coroutine coroutine ( runBlocking
start 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
CoroutineScope
Lane. - 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. CoroutineScope
to do it: create a CoroutineScope
target binding and activity of the life cycle, or allow activity to achieve CoroutineScope
interface.
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
GlobalScope
Start coroutine are independent, their life only by the application of restrictions. That GlobalScope
started 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.
GlobalScope
Start 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
- Coroutine Wiki
- Official documents Overview page
- Official documents Coroutines Guide
- Asynchronous Programming Techniques
- Your first coroutine with Kotlin
- Introduction to Coroutines and Channels
- Github: Kotlin/kotlinx.coroutines
- Github: Coroutines Guide
- Github: KEEP: Kotlin Coroutines
Third-party blog: