What is a coroutine?
- Coroutines are essentially blocks of code that run on threads, a set of threading APIS (Java-like Executor) operations provided by Kotlin.
- We’re talking about coroutines running on the JVM. There is no such thing as a coroutine in the JVM, so the coroutine doesn’t actually create anything from the bottom up, it just encapsulates the thread.
Why coroutines? What are the benefits of coroutines?
- Simple!!!
- Asynchronous operations can be written in synchronous code, or non-blocking code can be written in ways that appear to block
Eg: You need to request two apis, and then take the values of the two apis to do a consolidation, and finally display them on the page
- If you use a callback, you write it like this
// Callback mode
getCard(canId)
.enqueue(obj : Callback<Bitmap> {
...
override fun onResponse(call:Call<Bitmap>,
response: Resoinse<Bitmap>) {
val card = resonse.body()
getSofList(canId)
.enqueue(obj : Callback<Bitmap> {
...
override fun onResponse(call:Call<Bitmap>
response: Resoinse<Bitmap>) {
val sofList = response.body()
show(suspendMerge(cardDetail, sofList)) // Merge two pieces of information & UI display})}})Copy the code
The 👆 code is implemented as a callback. What’s wrong with it? Two network requests are originally parallel network requests, but they need to be presented together. If the callback method is used, it is made serial, and the network waits twice as long.
- How about using Kotlin’s coroutine?
/ / coroutines
suspend fun showCard(a){
val cardDetail = getCard(canId) // Get card details
val sofList = getSofList(canId) // Get bank card information
val cardInfo = suspendMerge(cardDetail.await, sofList.await) // Merge two messages
show(cardInfo) / / the UI display
}
Copy the code
Wow!!!! Is fried chicken neat and clear 😄 by the way, that’s why we use the Kotlin coroutine. We don’t need to know what’s going on in this, just for the moment, coroutines make things that would otherwise be very complicated to write with callback clean and clear.
Suspend function (suspend function)
Suspend is a keyword. What about the function suspend? -> Suspend the function
So what is a suspend function?
Suspend: suspend: suspend: suspend: suspend: suspend: suspend: suspend
fun getCard(a){... }Copy the code
Suspend function writing:
suspend fun getCard(a){... }Copy the code
The suspend keyword does:
Remind the caller of this function: I am a time-consuming function, please call me in the coroutine.
Suspend functions
Why do we use suspend functions? When A function is suspended from thread A, two lines appear:
- Thread A: Do what needs to be done, such as refresh the interface, or be recycled when there is nothing else to do
- Suspend function: switch to the specified thread, start from the suspended line of code, continue to execute the code, when finished, cut back to thread A
For example, in Android development, the Main thread is executing a task. When N executes a network request (suspend function), N detach from the Main thread and make a network request to the specified IO thread. Then the Main thread will continue to render the interface (e.g. loading in a loop). After N completes the IO thread, it will cut back to the Main thread and continue to perform subsequent operations (e.g. displaying the data returned after loading ends).
Suspend function problem
- The suspended object is a coroutine
- Suspended functions can only be called from within another suspended function or coroutine: why? Because cutting and resume is coroutine stuff, it can only be called from coroutine
The principle of
Ok, now you know what the suspend function is, but what about the implementation or how it works? Let’s go through this slowly. Suspending is simply suspending a function with the keyword suspendin front of it, and there’s no suspendin the Java platform, and there’s no suspending mechanism, so how does Kotlin get to the point where it’s possible to write code that doesn’t block in ways that seem to block?
The CPS transform
The Kotlin compiler does a special job of converting codes to ease corruption. So what does the Kotlin compiler do? In brief, the following three processes are mainly done:
Add a Continuation parameter and return Any? Treatment 2: Generates a Continuation type of anonymous internal class Treatment 3: Invocation of suspending methods into switch form of state machine
Let’s look at the most fundamental changes:
// Suspend the function signature of the function
suspend fun <T> CompletableFuture<T>.await(a):
Copy the code
After the change
fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?
Copy the code
So you can see, what does the compiler do? A Continuation is passed in and the return type changes to Any?
- the
Continuation
What is?
An interface: Kotlin’s interface can contain both declarations and implementations of abstract methods. Unlike abstract classes, interfaces cannot hold state. It can have attributes but must be declared abstract or provide accessor implementations. (Kotlin interface official explanation)
interface Continuation<in T> {
val context: CoroutineContext
fun resumeWith(result: Result<T>)
Copy the code
Any?
What is? Indicates that the type returned can be any type.
So, here’s the question, for example, a function that originally returned String, why is it changed to Any? After compiling, a token COROUTINE_SUSPENDED will be returned as well as the original value, so there is no corresponding type to match the result, so it becomes a class Any? Type. ? It can be null.
Before we get to the principle, let’s look at how to write a callback: Normally print a 6
private fun printSix(a) {
println("6")}Copy the code
Kotlin implements this with a callback: pass a Printer to the function and expose the implementation to the caller.
interface Printer{
fun print(a)
}
private fun printSix(printer: Printer) {
printer.print()
}
fun callPrintSix(a) {
printSix(object : Printer {
override fun print(a) {
// The concrete implementation is exposed to the caller
println("6")
Log.i("tag"."6")... }})}Copy the code
Now that you know how Kotlin writes the callback, let’s go back to what’s changed in our suspend function:
suspend fun getCard(cardNum : String) {
val card = api.getCard(cardNum)
}
Copy the code
After going through the compiler:
interface Continuation<in T> {
val context: CoroutineContext
fun resumeWith(result: Result<T>)
}
fun getCard(cardNum: String.continuation: Continuation) {
/ / new a continuation
val newContinuation = object: Continuation({
override resumeWith(..) {
getCard(this) // Pass the newContinuation from new into the getCard function itself
// This is a recursion}})/ / state machine
switch (newContinuation.label) {
case 0:
newContinuation.cardNum = cardNum
newContinuation.label = 1
return api.getCard(cardNum, newContinuation)
case 2:
val card = newContinuation.result
return card
}
}
Copy the code
In fact, if you read it carefully, the Kotlin coroutine is a callback + recursion + state machine that allows us to write asynchronous code in a simple and synchronous way. Summary: Each suspend method is compiled with an additional argument of type Continuation. Each suspend function has a callback to its own Continuation implementation class, and that class is passed to other suspending methods that the suspend function calls, which can be restored by the Continuation callback parent.
cps (continuation-passing style)
Continuation programming style, CPS is actually a programming style, that is, a callback style.
- Where continuation is an interface that contains one
resume
Function to restore a thread to its original thread. For example, you start on the Main thread, cut to the IO thread to make the request, and end up calling the Main functioncontinuation.resume()
So it’s going to cut back to main, just because it’s called by main. - Based on the code above, we see that each time the parent continuation is passed to the child method, and so on and so forth, if the return does not return the flag mentioned above
COROUTINE_SUSPENDED
, indicating that execution has finished, the parent of the original layer down will be calledcontinuation.resume()
And returns the result. (Corresponding to the state machine does not have the value of label, if there is a further step, the return will continue to contain the value of label and result)
Here’s what I found online
conclusion
Nature:
-
A coroutine is essentially a thread framework, a block of code on a thread. Nothing new was created.
-
Implementing CPS changes through the compiler allows developers to write asynchronous code in a simplified, synchronous manner.
Principle:
-
Each SUSPEND function is compiled with an input parameter of type Continuation, which is used to implement the callback. Change the return value to Any? Type, which can represent either the actual result or the execution status of a Coroutine.
-
The compiler produces an anonymous inner class of type Continuation for the suspend method (extending CoroutineImpl) that can be used as a callback to the suspend method itself and, after the suspend method has finished executing, Callback the parent method one level above the suspend method.
-
Finally, if the suspend method calls other suspend methods, it converts those calls to a state machine in the form of a switch, with each case representing a call to one of the suspend child methods or a final return. At the same time, the generated Continuation anonymous inner class stores the label value of a Kickable method that needs to be invoked next, indicating which case in the switch to execute, and thus wires the process together.