Summary of Kotlin coroutines
Article directory
- 1. What is a coroutine
-
-
- 1. Introduction
- 2. What is a coroutine
-
-
- 1. Introduction
- 2. li>
- 3. How to use the coroutine
-
- a. Add dependencies
- b. Start using
- c.suspend
- 4. Summary
-
- Second, what is hanging
-
-
- 1. The essence of “hanging”
-
- Thread:
- Coroutine:
- 2. Why is it “hanging”?
- 3. What is the meaning of suspend?
- 4. How to customize the suspend function?
-
- a. When do you need to customize the suspend function
- a. How to write it specifically
- 5. Summary
-
- Third, what is the non-blocking type of suspension
-
-
- 1. What is “non-blocking type suspension”
- 2. Why talk about non-blocking suspension
- 3. Coroutines and threads
- 4. Summary
-
- Fourth, Summary
1. What is a coroutine
1. Introduction
Coroutines are not a new concept proposed by Kotlin. Some other programming languages, such as Go, Python, etc., can implement coroutines at the language level, and even Java can also be indirectly supported by using extended libraries. Coroutine.
“Coroutines” is derived from the Simula and Modula-2 languages. This term was invented by Melvin Edward Conway as early as 1958 and used to build assembly programs, which shows that
coroutines are a programming idea
, and are not restricted to a particular language.When we understand
coroutine
, we will inevitably compare it withthread
andprocess
for analysis. Here is a post The picture is easy to understand
Understand their relationship from the perspective ofAndroid developers
:- All of our code runs in threads, and threads run in processes.
- The coroutine is not directly related to the operating system, but it is not a castle in the air. It also runs in a thread, which can be single-threaded or multi-threaded.
- The total execution time of a coroutine in a single thread will not be less than that without a coroutine.
- On the Android system, if a network request is made on the main thread,
NetworkOnMainThreadException
will be thrown, and the coroutine on the main thread is no exception. To cut the thread.
The original intention of the coroutine design is to solve the concurrency problem and make it more convenient to implement “cooperative multitasking”.
Coroutines are a set of thread-encapsulated APIs provided by Kotlin, but it does not mean that coroutines are born for threads.
However, when we learn coroutines in Kotlin, we can indeed start from the perspective of thread control. Because in Kotlin, a typical usage scenario of coroutines is thread control. Just like
Executor
in Java andAsyncTask
in Android, coroutines in Kotlin also encapsulate the Thread API, so that we can write code without paying attention to multi-threading It is very convenient to write concurrent operations.The following example uses a coroutine to make a network request to obtain user information and display it on the UI control:
launch({ val user = api.getUser() // 👈 network request (IO thread) nameTv.text = user.name // 👈 update UI (main thread) })
This is just a code snippet.
launch
is not a top-level function, it must be used in an object. The launch function plus the specific logic implemented in {} constitutes a Coroutine.Usually when we make a network request, we either pass a callback or make a blocking synchronous call in the IO thread. In this code, the upper and lower statements work in two threads respectively, but It looks the same as normal single-threaded code.
The
api.getUser
here is asuspended function
, so it can ensure the correct assignment ofnameTv.text
, which involves the protocol ProcessThe most famous “non-blocking hang”. This noun does not seem so easy to understand, and I will talk about it later. Put this concept down for now, just remember that coroutines are written like this.This kind of “writing asynchronous code in a synchronous way” seems very convenient, so let’s take a look at the specific advantages of coroutines.
2. Where is the coroutine?
As mentioned earlier, the launch function is not a top-level function and cannot be used directly. You can use the following three methods to create a coroutine:
// Method 1, use the runBlocking top-level function runBlocking { getImage(imageId) } // Method 2, use the GlobalScope singleton object // 👇 You can directly call launch to start the coroutine GlobalScope. launch { getImage(imageId) } // Method 3, create a CoroutineScope object through CoroutineContext // 👇 expects a parameter of type CoroutineContext val coroutineScope = CoroutineScope(context) coroutineScope. launch { getImage(imageId) }
- Method 1 is usually suitable for unit testing scenarios, but this method is not used in business development because it is thread-blocked.
- The difference between method two and using runBlocking is that the thread will not be blocked. But this usage is also not recommended in Android development, because its life cycle will be consistent with the app and cannot be canceled (what is the cancellation of the coroutine will be discussed later).
- The third method is the recommended method, we can manage and control the life cycle of the coroutine through the context parameter (the context here is not the same thing as in Android, it is a more generalconcept, there will be an Android platform package for use).
The most commonly used function of coroutines is concurrency, and the typical scenario of concurrency is multithreading. You can use the
Dispatchers.IO
parameter to switch the task to the IO thread for execution:CoroutineScope(Dispatchers.IO).launch { ... }
You can also use the
Dispatchers.Main
parameter to switch to the main thread:CoroutineScope(Dispatchers.Main).launch{ ... }
So the complete example of the asynchronous request mentioned in the section “What is a coroutine” is written like this:
CoroutineScope(Dispatchers.Main).launch{ // Start the coroutine on the main thread val user = api.getUser() // IO thread executes network request nameTv.text = user.name // The main thread updates the UI }
To implement the above logic in Java, we usually need to write like this:
api. getUser(new Callback() { @Override public void success(User user) { runOnUiThread(new Runnable() { @Override public void run() { nameTv.setText(user.name); } }) } @Override public void failure(Exception e) { ... } });
This callback-style writing method breaks the sequential structure and integrity of the code, making it quite uncomfortable to read.
3. How to use the coroutine
a. Add dependencies
//Depend on coroutine core library implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5' //Depend on the platform library corresponding to the current platform implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
Kotlin coroutines are supported in the form of official extension libraries. The version of “core library” and “platform library” we use should be consistent.
- The code contained in the core library is mainly the public API part of the coroutine. With this layer of common code, the interface of the coroutine on each platform is unified.
- The code contained in the platform library is mainly the specific implementation of the coroutine framework on a specific platform. Because the implementation of multithreading on each platform is different.
b. Get Started
The simplest way to use coroutines has already been seen in the previous chapters. We can realize the function of thread switching through a
launch
function:CoroutineScope(Dispatchers.IO).launch { ... }
The specific meaning of this launch function is: I want to create a new coroutine and run it on the specified thread. Who is this so-called “coroutine” that is created and run? It is the code you pass to launch. This piece of continuous code is called a “coroutine”.
So, when to use coroutines? When you need to cut threads or specify threads. Do you want to perform tasks in the background? cut!
launch(Dispatchers.IO) { val image = getImage(imageId) }
And then need to update the interface in the foreground? Cut again!
CoroutineScope(Dispatchers.IO).launch{ val image = getImage(imageId) launch(Dispatch. Main) { avatarIv. setImageBitmap(image) } }
Something seems wrong? Isn’t this still nested?
If you just use the launch function, coroutines can’t do more than threads. However, there is a very practical function in the coroutine:
withContext
. This function can switch to the specified thread, and automatically switch the thread back to continue execution after the execution of the logic in the closure is completed. Then the above code can be written like this:CoroutineScope(Dispatchers.Main).launch { // 👈 starts on UI thread val image = withContext(Dispatchers.IO) { // 👈 switch to IO thread, and switch back to UI thread after execution is complete getImage(imageId) // 👈 will run on the IO thread } avatarIv.setImageBitmap(image) // 👈 back to UI thread to update UI }
This way of writing seems to be not much different from the one just now, but if you need to switch threads frequently, the advantages of this way of writing will be reflected. You can refer to the following comparison:
// The first way of writing CoroutineScope(Dispatchers.IO). launch { ... launch(Dispachers. Main){ ...launch(Dispachers.IO) { ... launch(Dispacher. Main) { ... } } } } // Implement the same logic through the second way of writing CoroutineScope(Dispatchers. Main). launch{ ... withContext(Dispachers.IO) { ... } ... withContext(Dispachers.IO) { ... } ... }
Because it can “automatically switch back”, the nesting of concurrent codes during collaboration is eliminated. We can even put
withContext
into a separate function because of the elimination of nesting:launch(Dispachers.Main) { // 👈 starts on UI thread val image = getImage(imageId) avatarIv.setImageBitmap(image) // 👈 automatically switch back to UI thread after execution } // 👇 fun getImage(imageId: Int) = withContext(Dispatchers.IO) { ... }
This is what I said before “write asynchronous code in a synchronous way”.
But if you just write like this, the compiler will report an error:
fun getImage(imageId: Int) = withContext(Dispatchers.IO) { // IDE reports an error Suspend function'withContext' should be called oonly from a coroutine or another suspend funcion }
It means that withContext is a
suspend
function, which needs to be called in a coroutine or anothersuspend
function.c. suspend
suspend
is the core keyword of Kotlin coroutines, almost all articles and speeches introducing Kotlin coroutines will mention it. It means “pause” or “can be suspended” in Chinese. If you read some technical blogs or official documents, you can probably understand: “When the code executes to thesuspend
function, it will “suspend”, and this “suspend” is non-blocking, It will not block your current thread.”The above error code can be compiled by adding a
suspend
in front of it:suspend fun getImage(imageId: Int) = withContext(Dispatchers.IO) { ... }
What is
suspend
specifically, as described below.4. Summary
Coroutine is a kind of programming idea, which is written concisely and can be switched to a specified thread through the Dispatchers scheduler. All code runs in threads, and so do coroutines.
Second, what is hangup
1. The nature of “hang”
What exactly is the “suspended” object in the coroutine? Suspend the thread, or suspend the function? Neither, the object we are suspending is a coroutine.
The coroutine created by launch, async or other functions will be “suspend” when a certain suspend function is executed, that is, it will be suspended.
Then where did it hang up at this time? Suspend from current thread. In other words, the coroutine is detached from the thread that is executing it.
Attention, not this coroutine stopped! It is to leave, the current thread no longer cares what the coroutine is going to do.
suspend means to suspend, but we should understand it in the coroutine: when the thread executes the suspend function of the coroutine, it will not continue to execute the coroutine code for the time being.
Let’s let time stand still first, and then divide into two ways to see what will happen next to these two separate threads and coroutines:
Thread:
We mentioned earlier that suspending the coroutine will separate it from the thread that is executing it. The specific code is actually:
In the code block of the coroutine, when the thread executes to the suspend function, it will temporarily stop executing the remaining code of the coroutine and jump out of the code block of the coroutine.
What will the thread do next?
If it is a background thread:
- Either have nothing to do, be recycled by the system
- Or continue to perform other background tasks
The thread in the Java thread pool is exactly the same after the work is over: recycling or reuse.
If this thread is the main thread of Android, then it will continue to go back to work: that is, the interface refresh task 60 times a second.
Coroutine:
The code of the thread is cut off when it reaches the
suspend
function, and then the coroutine will continue to execute from thissuspend
function, but it is inSpecified thread
.Who specified? It is specified by the suspend function, such as the IO thread specified by
Dispatchers.IO
passed in bywithContext
inside the function.Dispatchers scheduler, which can limit coroutines to oneA specific thread execution, or dispatch it to a thread pool, or let it run unrestricted.
There are three commonly used
Dispatchers
:- Dispatchers.Main: The main thread in Android
- Dispatchers.IO: Optimized for disk and network IO, suitable for IO-intensive tasks, such as: reading and writing files, operating databases And network requests
- Dispatchers.Default: suitable for CPU-intensive tasks, such as computing
Back to our coroutine, it breaks away from the thread that started it from the
suspend
function, and continues to execute the IO thread specified inDispatchers
.Immediately after the execution of the suspend function is completed, the coolest thing the coroutine does for us comes: it will
automatically switch the thread back for us
.What does this “cut back” mean?
Our coroutine originally ran on the main thread. When the code encounters the
suspend
function, thread switching occurs, and it switches to the IO thread according toDispatchers
;When this function is executed, the thread is switched back. “Switching back” means that the coroutine will help me
post
aRunnable
, leaving me with The code continues to return to the main thread to execute.ok, after we have analyzed both threads and coroutines, we can finally explain the “suspend” of coroutines:
When the coroutine is executed to a function marked with suspend, it will be suspended, that is, suspended, and the so-called suspended means cutting a thread;
But the difference is that after the execution of the suspending function is completed, the coroutine will switch back to its original thread.
To put it simply, the so-called suspension in Kotlin is a thread scheduling operation that will be automatically switched back later.
This “cut back” action is called resume in Kotlin, recovery.
Through the analysis just now, we know that after the suspension, it needs to be resumed.
The recovery function is a coroutine. If you do not call it in the coroutine, the recovery function cannot be realized, so this question is answered: why must the suspend function be in a coroutine or another suspend function? is called.
Because a suspending function is either called in a coroutine or in another suspending function, it will always be called in a coroutine no matter directly or indirectly.
Of course, the requirement that the suspend function can only be called in a coroutine or another suspend function is to allow the coroutine to switch back after the suspend function switches threads.
2. Why is it “hanging”?
After we understand what “hanging” is, let’s take a look at how this “hanging” is done.
First write a custom
suspend
function:suspend fun suspendingPrint() { println("Thread: ${Thread. currentThread(). name}") } I/System.out: Thread: main
The result of the output is still in the main thread.
Why didn’t you switch threads? Because it doesn’t know where to go, we need to tell it.
For example,
suspendingGetImage
function code in the example://👇 suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO) { ... }
We can find that the difference is actually in the
withContext
function.In fact, you can know from the source code of
withContext
that it is a suspend function itself, which receives aDispatcher
parameter, and depending on the indication of this Dispatcher parameter, your coroutine is executed Hang up and switch to another thread.So this suspend does not actually play the role of suspending any coroutines, or switching threads.
The matter of actually suspending the coroutine is what Kotlin’s coroutine framework does for us.
So we want to write a suspend function by ourselves. It is not enough to just add the suspend keyword. It is also necessary to directly or indirectly call the suspend function that comes with the Kotlin coroutine framework inside the function.
3. What is the meaning of suspend?
The suspend keyword, since it does not really implement suspension, what is its function?
It is actually a reminder.
The creator of the function reminds the user of the function: I am a time-consuming function, and I am put in the background by my creator in a suspended manner, so please call me in the coroutine.
Why does the
suspend
keyword not actually operate the suspension, but Kotlin provides it?Because it is not intended to be used for operation suspension.
The suspended operation—that is, thread cutting, depends on the actual code in the suspended function, not on this keyword.
So this keyword, is just a reminder.
Remember how we tried to customize the suspend function just now?
// 👇 redundant suspend modifier suspend fun suspendingPrint() { println("Thread: ${Thread. currentThread(). name}") }
If you create a suspend function but it does not contain real suspend logic inside, the compiler will give you a reminder: redundant suspend modifier, telling you that this
suspend
is redundant.Because your function does not actually suspend, your suspend keyword has only one effect: it is to restrict this function to be called only in the coroutine, if it is called in the non-coroutine code, it will Compilation fails.
So, to create a suspend function, in order to make it contain the real suspend logic, you must directly or indirectly call the suspend function that comes with Kotlin inside it, your
suspend
is meaningful of.4. How to customize the suspend function?
After understanding the ins and outs of the suspend keyword, we can enter the next topic: how to customize the suspend function.
This “how to customize” is actually divided into two questions:
- When do you need to customize the suspend function?
- How should I write it?
a. When do you need to customize the suspend function
If one of your functions is time-consuming, that is, an operation that needs to wait, write it as a suspend function. This is the principle.
Time-consuming operations generally fall into two categories: I/O operations and CPU computing work. For example, the reading and writing of files, network interaction, and image blurring are all time-consuming, and they can all be written into the suspend function.
In addition, this “time-consuming” has a special situationThe situation is that it is not slow to do this thing itself, but it needs to wait, for example, 5 seconds before doing this operation. This is also the application scenario of the suspend function.
a. How to write in detail
Add the suspend keyword to the function, and then wrap the content of the function in withContext.
The use of withContext is mentioned because it has the simplest and most direct function in the suspend function: automatically switch the thread away and back.
Of course, there is not only the withContext function to assist us in implementing a custom suspend function. For example, there is also a suspend function called delay, which is used to wait for a period of time before continuing to execute code.
Using it can realize the time-consuming operation of the waiting type just mentioned:
suspend fun suspendUntilDone() { while (!done) { delay(5) } }
When we first use coroutines, we don’t need to touch them immediately. We can first understand the most basic methods and concepts of coroutines.
5. Summary
This part is mainly about suspending. This suspending object is a coroutine, which is a code block.
Suspending is actually switching threads, and coroutines can also help us switch back.Third, what is the hanging non-blocking method
1. What is “non-blocking suspension”
Non-blocking is relative to blocking.
Thread blocking is easy to understand. The real example is traffic jam. Its core has 3 points:
- There are obstacles in front of you, you can’t get through (the thread is stuck)
- You need to wait for the obstacles to be cleared before you can pass (the time-consuming task ends)
- Unless you make a detour Line (cut to another thread)
Understanding “non-blocking suspension” semantically means that “non-blocking” is a feature of suspension, that is to say, the suspension of coroutines,It is non-blocking, and the coroutine does not talk about the concept of “blocking suspension”.
We talk about “non-blocking suspension”. In fact, it has several prerequisites: it is not limited to one thread to talk about this matter, because the matter of suspension originally involves multi-threading.
Blocking and non-blocking are all about single thread. Once the thread is cut, it must be non-blocking. If you run to other threads, the previous thread will be free and you can continue to do other things.
So “non-blocking suspension” is actually talking about the thread cutting while the coroutine is suspended.
2. Why talk about non-blocking suspension
The coroutine only “looks blocking” in writing, but it is actually “non-blocking”, because it does a lot of work in the coroutine, one of which is to help us cut threads.
Let’s look at the following example:
main { CoroutineScope(Dispatchers. Main). launch { // 👇 time-consuming operation val user = suspendingRequestUser() updateView(user) } private suspend fun suspendingRequestUser() : User = withContext(Dispatchers.IO) { api. requestUser() } }
From the above example, we can see that the time-consuming operation and the logic of updating the UI are put together like writing a single thread, but a layer of coroutines is wrapped outside.
And it is this coroutine that solves the problem that our single-threaded writing method will get stuck in the thread.
3. Coroutines and threads
In Kotlin, a coroutine is a higher-level tool API implemented based on threads, similar to the Executor series APIs that come with Java or the Handler series APIs in Android.
justWell, coroutines not only provide a convenient API, but are designed as a
thread-based upper framework
. You can understand that some new concepts have been created to help you use these APIs better. That’s all.Just like ReactiveX, in order to allow you to better use various operator APIs, new concepts such as Observable have been created.
4. Summary
This part mainly talks about non-blocking suspension. Suspending is actually switching threads, that is, leaving the original thread. What should the original thread do, so it is non-blocking suspension.
Four. Summary
- Coroutine is thread cutting;
- Suspension is thread cutting that can be automatically switched back;
- The non-blocking type of suspension means that it can be used to see It’s that simple to write non-blocking operations from blocking code.
Reference:
1, Kotlin’s coroutines take a hard look – can’t learn coroutines? Probably because the tutorials you have read are all wrong
2. The suspension of Kotlin coroutines is so magical and difficult to understand? Today I peeled off its skin
3, what is “non-blocking” suspension? Are coroutines really more lightweight?
4. The actual combat of Kotlin coroutine
5. Comics: What is a coroutine?Congratulations to the big guys.
-