Kotlin | The understanding and use of coroutines
Article directory
-
-
- What is a coroutine?
- Characteristics of coroutines
- How to use coroutines
- Start using coroutines
- Case 1:
- suspend
-
- thread
- coroutine
- The suspension of suspend
- The meaning of suspend
- When do you need to customize the suspend function
-
- Suspend Type of function
- Write the callback as a suspend function
- Case 2
- Non-blocking suspension
- The specific use of the coroutine
-
- delay
- runBlocking
- Waiting for a task
- Structured concurrency
-
What is a coroutine?
In fact, it is a set of thread API officially provided by Kotlin. Asynchronous code can be executed in a very elegant way. Simple to use and efficient.
Characteristics of coroutines
- Structured Concurrency:
val scope = CoroutineScope(Dispatchers.Main).launch {
launch {
launch {
}
}
launch {
}
}
scope. cacel()
Coroutines support nesting, and have the concept of sub-coroutines and parent coroutines
As above, after starting a coroutine, multiple sub-coroutines are started internally, in the sub-coroutine You can also continue to start the coroutine. Looking back at threads, threads can be created in threads, but they are not related
This feature of coroutines is called structured concurrency. This method allows coroutines to be managed very conveniently. For example, to close a coroutine, you only need to close the outermost coroutine, and the internal coroutines will be closed closure. For sub-coroutines, you can also get its return value and call cacel to close it.
- Cancellation of the coroutine
- Exception handling of the protocol
How to use the coroutine
Configure support for Kotlin’s coroutines in the project
//core library
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.1"
//Depend on the library corresponding to the current platform
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.1"
Kotlin coroutines are supported in the form of official extension libraries. And the version of core library and platform library should be consistent.
- The code contained in the core library is mainly the public API part of the coroutine. With this layer of code, the interface of the coroutine on each platform is unified
- Platform library The code contained in is mainly the specific implementation of the coroutine framework on a specific platform. Because multithreading is different on each platform.
Start using coroutines
//The meaning of the launch function: I want to create a new coroutine. and run it on the specified thread
CoroutineScope(Dispatchers.IO).launch {
println(Thread. currentThread(). name)
}
Can be nested
CoroutineScope.launch(Dispatchers.IO) {
val image = getImage(imageId)
launch(Dispatch.Main) { //will run on the Main thread
avatarIv. setImageBitmap(image)
}
}
This doesn’t do much if it’s just nested. Coroutines have a very useful function: withContext. This function can switch to the specified thread, and automatically switch the thread back to continue execution after the logic in the closure is executed
CoroutineScope(Dispatchers.Main).launch {
println(Thread. currentThread(). name)
val bitmap = withContext(Dispatchers.IO) { //switch to IO thread
getImage()
}
iv.setImageBitmap(bitmap) //main thread update
}
Since it can be automatically switched back, we can even put withContext into a separate function, as follows:
suspend fun getImage(): Bitmap = withContext(Dispatchers.IO) {
//.....
}
But watch out for the suspend keyword. This keyword is said behind, his Chinese meaning is to suspend or to be suspended.
Case 1:
Download a network picture through the coroutine and display it
override fun bindView(view: View) {
val iv = view.findViewById(R.id.delegate_shop_iv)
btn = view.findViewById(R.id.delegate_shop_btn)
btn.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
val bitmap = getImage()
iv.setImageBitmap(bitmap)
}
}
}
private suspend fun getImage(): Bitmap = withContext(Dispatchers.IO) {
OkHttpClient().newCall(Request.Builder().url("https://dss0.bdstatic.com/6Ox1bjeh1BF3odCf/it/u=4256581120,3161125441&fm=193")
.get()
.build())
.execute().body()?.byteStream().use {
BitmapFactory. decodeStream(it)
}
}
suspend
The coroutine refers to the code in the launch, so what is the suspension in the coroutine? In fact, the suspended object is the coroutine
When executed in launch, when a certain suspend function is executed, the coroutine will be suspended. let
Time is still, and the soldiers are divided into two groups. Let’s see what’s going on. These two routes are coroutines and threads (UI threads)
Thread
When the code executes to the suspend function in the coroutine, the code of the coroutine will not be executed temporarily, but will jump out of the code block of the coroutine. Continue to execute down.
CoroutineScope(Dispatchers.Main).launch {
val bitmap = getImage() //hangs
iv.setImageBitmap(bitmap)
}
ToastUtils.show("hahahaha")
When the main thread executes getImage, it will jump out of the coroutine and execute the following Toast.
This coroutine will essentially post a Runnable to the main thread. Then continue to execute the internal code of the coroutine. When the execution is suspended, the Runnable will end early, and the thread will continue to execute other things. The coroutine will be suspended. So let’s take a look at the coroutine
Coroutine
The main thread will be cut off when it reaches suspend, and then the coroutine will continue to execute. But it is executed in the specified thread. The IO thread specified by the Dispatchers.IO passed in withContext
Dispatchers Scheduler: Limit coroutine execution to a specific thread, or dispatch it to a thread pool.
Scheduler for daily use: Main: Android main thread, IO: network IO, Default: suitable for CPU-intensive tasks, such as computing.
The coroutine starts to execute in the specified thread from suspend, and after execution, it will automatically switch us back to the thread.
Switching back means switching to the original thread. If it was originally running on the main thread, it will continue to execute on the main thread after switching back. That is to say, the coroutine will help us post a Runnable to the main thread.
Through the above two angles, one can get an explanation: the coroutine will be suspended when it is executed until there is suspend, and this suspension is to cut a thread; but the suspended one will be restarted after execution Switch back to his original thread
This switching action is called resume in Kotlin, resume
suspend’s suspension
A suspend function is a function modified with suspend. A suspend function can only be used in other suspend functions or a coroutine
This is a keyword. But he wasn’t really hanging. You can write a function with supend, and after running it, you will find that it is not suspended. Why it is not suspended, it should be because it does not know where to go, and we need to tell him. As follows:
suspend fun get() = withContext(Dispatchers.IO) {
}
withContext itself is a suspending function that receives a Dispatcher
parameter, and he must rely on this parameter to know that the coroutine needs to be suspended. Then switch to another thread
So suspend can’t play the role of any suspend function, the suspend function is done for us by kt’s coroutine
The meaning of suspend
Why does the suspend keyword not actually suspend, but why does Kotlin provide it?
Because he was not originally used to operate the hang. That is to say, cutting threads depends on the code in the function, not this keyword. So this keyword is just for reminder.
If you create a suspend function, but it does not contain real suspend logic inside, the compiler will remind you: Redundant ‘suspend’ modifier, this keyword is redundant
Because this function does not hang, this suspend has only one effect: restrict this function to be called only in coroutines, if it is called in non-coroutines, the compilation will not pass
Therefore, to create a suspend function, in order to allow it to include suspend, it is necessary to directly or indirectly call the suspend function that comes with Kotlin internally. At this time, the function is meaningful
When do you need to customize the suspend function
If you have a function that is time-consuming, that is, you need to wait, you can write it as a suspend function
Add the suspend keyword to the function, and then wrap the function content in withContext. Of course, withContext is not the only way to assist us in implementing custom functions, such as delay , whose function is to wait for a period of time before continuing to execute the code.
suspend fun get() {
delay(5) //suspend
}
Type of suspend function
suspend fun foo(){}
If there is no suspend, the type of this function is ()-> Unit
But after adding suspend, the type of the function is suspend()-> Unit
As long as you know the type of the function, Then add a suspend in front to suspend the type of the function
suspend fun bar(a:Int):String{
return "Hello"
}
The type of the suspend function bar is suspend(Int)->String
Remember why suspend can only be called in suspend function or coroutine?
Because all suspending functions have a Continuation parameter, where does the Continuation come from? The suspend keyword will implicitly add a Continuation parameter at the end of the function’s parameter list. The generic parameter of the Continuation is determined by the return value of the function
The final appearance of the suspending function bar above is as shown in the figure above. It will add a parameter at the end of the parameter list, and the return value will be Any. This Any has two cases, if this function is not really suspended, such as bar function, foo function. When the function is not actually suspended, this Any is used to carry the return value result. If the function is really suspended, this Any returns a suspended flag COROUTINE_SUSPENDED to let the external coroutine body know that my coroutine is really suspended. It is necessary to wait for the callback of this function before continuing to execute. So this Any is very important
Write the callback as a suspending function
Implemented by suspendCoroutine:
private suspend fun getImage() = suspendCoroutine {
continuation ->OkHttpClient(). newCall(Request. Builder()
.url("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1581869591580&di=e0412feb1e101a144e416f7a873bd88d&imgtype=0&src=http://i0.hdslb.com/bfs/art icle/6febb183087736d089b6583a790c491f2dc7469a.jpg")
.get()
.build())
.enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
continuation. resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
continuation.resume(response.body()?.byteStream().use { BitmapFactory.decodeStream(it)})
}
})
}
If you want to turn the callback into a suspension, you need to use the suspendCoroutine function call to get the Continuation of the current function. Continuation can be obtained through this method. This parameter is hidden by the compiler.
If the callback succeeds, you can use resumee or resumeWith returns the result
For exceptions, just use resumeWithException.
Case 2
The network requests a picture and cuts it twice. 1. Cut into four parts and take the first part. 2, cut 9 parts, go to the last one
override fun bindView(view: View) {
// getSupportDelegate().loadRootFragment(R.id.delegate_shop_layout,
// BaseShopListDelegate.newInstance(CarPreference.getMyCar(), BusinessScope.BUSINESSSCOPE_SHOP_LIST));
val iv1 = view.findViewById(R.id.delegate_shop_iv1)
val iv2 = view.findViewById(R.id.delegate_shop_iv2)
val iv3 = view.findViewById(R.id.delegate_shop_iv3)
btn = view.findViewById(R.id.delegate_shop_btn)
btn.setOnClickListener {
CoroutineScope(Dispatchers.Main).launch {
val bitmap = getImage()
iv1.setImageBitmap(bitmap)//Original imageval bm1 = Bitmap.createBitmap(bitmap, 0, 0, bitmap.width / 2, bitmap.height / 2)
iv2.setImageBitmap(bm1)
val bm2 = Bitmap.createBitmap(bitmap, bitmap.width / 3 * 2, bitmap.height / 3 * 2, bitmap.width / 3, bitmap.height / 3)
iv3.setImageBitmap(bm2)
}
}
}
private suspend fun getImage(): Bitmap = withContext(Dispatchers.IO) {
OkHttpClient(). newCall(Request. Builder()
.url("https://dss0.bdstatic.com/6Ox1bjeh1BF3odCf/it/u=4256581120,3161125441&fm=193")
.get()
.build())
.execute().body()?.byteStream().use {
BitmapFactory. decodeStream(it)
}
}
Non-blocking suspend
First of all, what is blocking?
1, there is an obstacle in front, I can’t get through it (thread card owner)
2. Clear obstacles (wait for time-consuming tasks to end)
3, take a detour (cut to another thread)
Non-blocking suspend is not limitedIn one thread, because the hang inherently involves multiple threads. When the main thread encounters a time-consuming task during execution, it then suspends the time-consuming task. At this time, the main thread is free and can continue to do other things. So non-blocking suspension is actually talking about switching threads when the coroutine is suspended.
The coroutine just looks like it will block, but it is actually non-blocking because it can cut threads
Coroutine and thread: In Kotlin, coroutine is a higher-level tool API based on thread implementation, but its usage is very simple.
What is a coroutine: a thread-based framework
Suspension of coroutines: automatic thread switching
Non-blocking suspension: Non-blocking operations can be implemented with seemingly blocking code
Specific use of coroutines
delay
fun main() {
GlobalScope. launch {
delay(1000L) //The coroutine is suspended and blocked for 1 second
println("2020")
}
print("hello") //When the coroutine is suspended, the main thread continues to execute
Thread.sleep(2000L)//delay: ensure the survival of the main thread
}
//hello 2020
run Blocking
When talking about creating coroutines, I said that runBlocking is blocking
fun main() {
GlobalScope. launch {
delay(1000L)
println("2020")
}
println("Hello")
//The expression blocks the main thread, The main thread that called runBlocking will wait until runBlocking is finished
runBlocking {
delay(2000L) //delay: ensure the survival of the main thread
}
}
//Hello 2020
Modify the code as follows:
fun main() = runBlocking {//Start executing the main coroutine
println(Thread. currentThread(). name)
GlobalScope. launch {
delay(1000L)
println("2020")
}
print("Hello")
delay(2000L) //delay: ensure the survival of the main thread
}
//main
//Hello 2020
In fact, it is still the main thread
Waiting for a task
suspend fun main() {
val job = GlobalScope. launch {
delay(1000L)
println("2020")
}
println("Hello")
job.join() //Wait for the sub-thread execution to end
}
Note that the main method is modified by suspend, because the join method is modified by suspend, and suspend itself will not suspend. The suspension is because there is suspended code inside the join. suspend is just a hint. Only this prompt must be written.
Structured Concurrency
fun main() = runBlocking {
//Start executing the main coroutine
launch {
delay(1000L)
println("2020")
}
print("Hello")
}
//Hello 2020
Note: At the end, the main thread is not made to wait, nor is join called. Why does it print out 2020?
Adds an instance of CoruntineScope to the scope of the code block in each coroutine builder inside runBlocking. We can start coroutines in this scope without calling join explicitly. Because the outer coroutine (runBlocking) will not end until all coroutines started in its scope have executed.
Refer to the video from the MOOC website
Refer to the throwing line
Refer to the official website