If we decompile the above suspended function into Java, the result will be as follows:
// Continuation is equivalent to CallBack // ↓ public static final Object getUserInfo(Continuation $completion) { ... return "BoyCoder"; }
From the result of decompilation, the suspended function has indeed become a function with CallBack, but the real name of the CallBack is Continuation. After all, it's too low to call CallBack directly, isn't it?
Let's look at the definition of Continuation in Kotlin:
public interface Continuation<in T> { public val context: CoroutineContext // Equivalent to onSuccess result // ↓ ↓ public fun resumeWith(result: Result<T>) }
Compare the definition of CallBack:
interface CallBack { void onSuccess(String response); }
From the above definition, we can see that Continuation is actually a CallBack with generic parameters. In addition, there is a CoroutineContext, which is the context of the collaboration. For small partners familiar with Android development, it's just context! There's nothing hard to understand, right?
The above process of converting a pending function into a CallBack function is called CPS transformation.
Look, Kotlin's official reason for using Continuation instead of CallBack is that Continuation shows its implementation principle. Of course, in order to understand the hang function, we use CallBack to make it easier to understand.
The following animation shows the change of function signature during CPS conversion of suspended function:
This transformation looks simple, and there are some details hidden in it.
Change of function type
During the CPS conversion above, the type of the function changes: suspend () - > string becomes (Continuation) - > any?.
This means that if you access a Kotlin pending function getUserInfo() in Java, the type of getUserInfo() in Java will be: (Continuation) - > object. (receive continuation is a parameter, and the return value is Object)
In this CPS conversion, suspend () becomes continuation, which we have explained earlier. It's not difficult. So why does the return value of the function change from: String to Any?
Return value of suspended function
After CPS conversion, the return value of a suspended function plays an important role: it indicates whether the suspended function has been suspended.
This sounds a little windy: a suspended function is a function that can be suspended. Can it not be suspended? Yes, suspended functions can also not be suspended.
Let's clarify several concepts:
As long as there is a function decorated with suspend, it is a suspended function. For example, our previous example:
suspend fun getUserInfo(): String { withContext(Dispatchers.IO) { delay(1000L) } return "BoyCoder" }
When getUserInfo() executes to withContext, it will return coroutinesimpletons.coroutine_ Suspended indicates that the function is suspended.
Now the question arises. Is the following function a suspended function
// suspend modification // ↓ suspend fun noSuspendFriendList(user: String): String{ // The function body is the same as an ordinary function return "Tom, Jack" }
Answer: it is a pending function. But it is different from the general suspended function: when it is executed, it will not be suspended because it is an ordinary function. When you write such code, the IDE will also prompt you that suspend is redundant:
When noSuspendFriendList() is called, it will not suspend. It will directly return the String type: "no suspend". Such a suspended function, you can think of it as a pseudo suspended function
The return type is Any? Reasons for
Because of the suspend modified function, it is possible to return coroutinesimpletons.coroutine_ Suspended may also return the actual result "no suspend", or even null. In order to adapt to all possibilities, the return value type of the function after CPS conversion can only be Any? Yes.
Summary
- The suspend function is the suspend function
- Suspended functions may not always be suspended during execution
- Suspended functions can only be called in other suspended functions
- Only when the suspended function contains other suspended functions will it really be suspended
The above is the details of function signature during CPS conversion.
However, this is not all about CPS conversion, because we don't know what Continuation is.
6. CPS conversion
The word Continuation, if you look it up in the dictionary(
)And [Wikipedia](
), it may be confused. It's too abstract.
It will be easier to understand Continuation through the examples in our article.
First, we just need to grasp the etymology of Continuation. Continue means to continue, and Continuation means to continue what to do and what to do next.
In the program, Continuation represents the code to be executed when the program continues to run, the code to be executed next, or the rest of the code.
Take the above code as an example. When the program runs getUserInfo(), its Continuation is the code in the red box below:
Continuation is the code to be run next, and the remaining unexecuted code.
After understanding Continuation, CPS is easy to understand. In fact, it is a mode of passing the code to be executed next.
CPS conversion is the process of converting the original synchronous pending function into CallBack asynchronous code. This conversion is done behind the scenes by the compiler, which is not perceived by our programmers.
There may be a little partner sniffing: so simple and rough? Do the three pending functions eventually become three callbacks?
Of course not. Thought is still the thought of CPS, but it is much better than Callback.
Next, let's take a look at the decompiled code of the pending function. There are so many mattings in front, all for the next part.
7. Byte code Decompilation
We have done this many times. Unlike usual, I will not post the decompiled code directly this time, because if I post the decompiled Java code directly, it is estimated that it will scare off a large group of people. The logic of the code decompiled by the coroutine is too convoluted and the readability is too poor. The CPU may like such code, but it's really not seen by people.
Therefore, in order to facilitate your understanding, the next code I post is roughly equivalent code after I translated it with Kotlin, which improves readability and erases unnecessary details. If you can understand everything in this article, you have more understanding of collaborative process than most people.
To get to the point, this is the object we will study, the code before decompilation of testCoroutine:
suspend fun testCoroutine() { log("start") val user = getUserInfo() log(user) val friendList = getFriendList(user) log(friendList) val feedList = getFeedList(friendList) log(feedList) }
After decompilation, the signature of testCoroutine function becomes as follows:
// No suspend, more completion fun testCoroutine(completion: Continuation<Any?>): Any? {}
Since several other functions are also suspended functions, their function signatures will also change:
fun getUserInfo(completion: Continuation<Any?>): Any?{} fun getFriendList(user: String, completion: Continuation<Any?>): Any?{} fun getFeedList(friendList: String, completion: Continuation<Any?>): Any?{}
Next, we analyze the function body of testCoroutine() because it is quite complex and involves calls to three suspended functions.
First, in the testCoroutine function, there will be a subclass of ContinuationImpl, which is the core of the whole coroutine suspension function. The comments in the code are very detailed. Please look carefully.
fun testCoroutine(completion: Continuation<Any?>): Any? { class TestContinuation(completion: Continuation<Any?>?) : ContinuationImpl(completion) { // Indicates the current state of the coroutine state machine var label: Int = 0 // Co process return result var result: Any? = null // Used to save the calculation results of previous collaboration var mUser: Any? = null var mFriendList: Any? = null // invokeSuspend is the key to collaborative process // It will eventually call testCoroutine(this) to start the coroutine state machine // The code related to the state machine is the following when statement // The essence of collaborative process can be said to be CPS + state machine override fun invokeSuspend(_result: Result<Any?>): Any? { result = _result label = label or Int.Companion.MIN_VALUE return testCoroutine(this) } } }
Next, you need to determine whether testCoroutine is running for the first time. If it is running for the first time, you need to create an instance object of TestContinuation.
// ↓ fun testCoroutine(completion: Continuation<Any?>): Any? { ... val continuation = if (completion is TestContinuation) { completion } else { // As a parameter // ↓ TestContinuation(completion) } }
- invokeSuspend will eventually call testCoroutine, and then go to the judgment statement
- If it is the first run, a TestContinuation object will be created with completion as the parameter
- This is equivalent to wrapping the old Continuation with a new Continuation
- If it is not the first run, assign completion to continuation directly
- This shows that only one instance of continuation will be generated during the whole operation, which can greatly save memory overhead (compared with CallBack)
Next are the definitions of several variables. Detailed comments will be provided in the code:
// Three variables, corresponding to the three variables of the original function lateinit var user: String lateinit var friendList: String lateinit var feedList: String // Result receives the running result of the collaboration var result = continuation.result // suspendReturn receives the return value of the suspended function var suspendReturn: Any? = null // CoroutineSingletons is an enumeration class // COROUTINE_SUSPENDED means that the current function is suspended val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED
Then we come to the core logic of our state machine. See the notes for details:
when (continuation.label) { 0 -> { // Abnormal detection throwOnFailure(result) log("start") // Set the label to 1 and prepare to enter the next state continuation.label = 1 // Execute getUserInfo suspendReturn = getUserInfo(continuation) // Determine whether to suspend if (suspendReturn == sFlag) { return suspendReturn } else { result = suspendReturn //go to next state } } 1 -> { throwOnFailure(result) // Get user value user = result as String log(user) // Save the collaboration results in the continuation continuation.mUser = user // Ready to go to the next state continuation.label = 2 // Execute getFriendList suspendReturn = getFriendList(user, continuation) // Determine whether to suspend if (suspendReturn == sFlag) { return suspendReturn } else { result = suspendReturn //go to next state } } 2 -> { throwOnFailure(result) user = continuation.mUser as String // Get the value of friendList friendList = result as String log(friendList) // Save the collaboration results in the continuation continuation.mUser = user continuation.mFriendList = friendList // Ready to go to the next state continuation.label = 3 // Execute getFeedList suspendReturn = getFeedList(friendList, continuation) // Determine whether to suspend if (suspendReturn == sFlag) { return suspendReturn } else { result = suspendReturn //go to next state } } 3 -> { throwOnFailure(result) user = continuation.mUser as String friendList = continuation.mFriendList as String feedList = continuation.result as String log(feedList) loop = false } }
- The when expression implements the coprocessor state machine
- continuation.label is the key to state flow
- If the continuation.label is changed once, it means that the collaboration process has been switched once
- After each coordination process switching, it will check whether there are exceptions
- The original code in testCoroutine is split into various states in the state machine and executed separately
- The three function calls getUserInfo(continuation), getFriendList(user, continuation) and getFeedList(friendList, continuation) pass the same continuation instance.
- If a function is suspended, its return value will be coroutinesimpletons.coroutine_ SUSPENDED
- Before switching the coprocess, the state opportunity saves the previous results in the continuation in the form of member variables.
Warning: the above code is an improved version of decompiled code written by Kotlin. I will also release the real code after decompilation. Please continue to read.
8. Animation demonstration of collaborative process state machine
Is it a little dizzy to see a large string of words and codes above? Please take a look at this animation demonstration. After reading the animation demonstration, look back at the text above, and you will gain more.
Is it over? No, because the above animation only shows the normal suspension of each collaboration. What if the process doesn't really hang up? How does the process status opportunity work?
The collaboration process is not suspended
It is also very simple to verify. We can change one of the suspended functions to a pseudo suspended function.
// "Pseudo" suspended function // Although it is decorated with suspend, it will not really hang when executed, because there are no other suspended functions in its function body // ↓ suspend fun noSuspendFriendList(user: String): String{ return "Tom, Jack" } suspend fun testNoSuspend() { log("start") val user = getUserInfo() log(user) // The change is here // ↓ val friendList = noSuspendFriendList(user) log(friendList) val feedList = getFeedList(friendList) log(feedList) }
What about the decompiled code logic of a function body like testNoSuspend()?
The answer is actually very simple. Its structure is the same as the previous testCoroutine(), but the function name has changed. The logic of CPS conversion in Kotlin compiler only recognizes the suspend keyword. Even if it is a "pseudo" suspended function, the Kotlin compiler will still perform CPS conversion.
when (continuation.label) { 0 -> { ... } 1 -> { ... // The change is here // ↓ suspendReturn = noSuspendFriendList(user, continuation) // Determine whether to suspend if (suspendReturn == sFlag) { return suspendReturn } else { result = suspendReturn //go to next state } } ## Share readers **[CodeChina Open source projects:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code]( )** > Author 2013 java go to Android Development, worked in small factories, and went to Huawei, OPPO After staying in a big factory, he entered Ali in April of 18 and has been here until now. > I have been interviewed and interviewed many people. I know most junior and intermediate students Android Engineers who want to improve their skills often grope and grow by themselves. The learning effect of fragmentation is inefficient and long, and it is very easy to encounter the stagnation of ceiling technology! We sorted out a copy, Ali P7 Rank Android A complete set of learning materials for architects, especially suitable for 3-5 Small partners with more than years of experience will learn and improve in depth. It mainly includes the mainstream architecture technologies of Tencent, byte beating, Alibaba, Huawei, Xiaomi and other first-line Internet companies. ![tencent T3 Architect learning materials](https://img-blog.csdnimg.cn/img_convert/7f1b866c8e9567615768e5fb07010bd4.png) **If you feel that your learning efficiency is low and you lack correct guidance, you can learn and communicate together!** We are committed to building an equal and high-quality company Android The communication circle may not make everyone's technology advance by leaps and bounds in the short term, but in the long run, vision, pattern and long-term development direction are the most important. 35 The middle-aged crisis is mostly due to being led by short-term interests and squeezing out value too early. If you can establish a correct long-term career plan at the beginning. After the age of 35, you will only be more valuable than the people around you. > **This article has been[tencent CODING Open source hosting project:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code](https://ali1024.coding.net/public/P7/Android/git), self-study resources and series of articles are constantly updated** Our little partners learn deeply and improve. It mainly includes the mainstream architecture technologies of Tencent, byte beating, Alibaba, Huawei, Xiaomi and other first-line Internet companies. [External chain picture transfer...(img-1oE8Qj8Q-1631257144912)] **If you feel that your learning efficiency is low and you lack correct guidance, you can learn and communicate together!** We are committed to building an equal and high-quality company Android The communication circle may not make everyone's technology advance by leaps and bounds in the short term, but in the long run, vision, pattern and long-term development direction are the most important. 35 The middle-aged crisis is mostly due to being led by short-term interests and squeezing out value too early. If you can establish a correct long-term career plan at the beginning. After the age of 35, you will only be more valuable than the people around you. > **This article has been[tencent CODING Open source hosting project:< Android Summary of study notes+Mobile architecture video+Real interview questions for large factories+Project practice source code](https://ali1024.coding.net/public/P7/Android/git), self-study resources and series of articles are constantly updated**