自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

再談協(xié)程之 Suspend 到底掛起了啥

開發(fā) 開發(fā)工具
Kotlin編譯器會(huì)給每一個(gè)suspend函數(shù)生成一個(gè)狀態(tài)機(jī)來管理協(xié)程的執(zhí)行。

[[437771]]

 Kotlin編譯器會(huì)給每一個(gè)suspend函數(shù)生成一個(gè)狀態(tài)機(jī)來管理協(xié)程的執(zhí)行。

Coroutines簡(jiǎn)化了Android上的異步操作。正如文檔中所解釋的,我們可以用它們來管理異步任務(wù),否則可能會(huì)阻塞主線程,導(dǎo)致你的應(yīng)用程序Crash。

Coroutines也有助于用命令式的代碼取代基于回調(diào)的API。

作為例子,我們先看看這個(gè)使用回調(diào)的異步代碼。

  1. // Simplified code that only considers the happy path 
  2. fun loginUser(userId: String, password: String, userResult: Callback<User>) { 
  3.   // Async callbacks 
  4.   userRemoteDataSource.logUserIn { user -> 
  5.     // Successful network request 
  6.     userLocalDataSource.logUserIn(user) { userDb -> 
  7.       // Result saved in DB 
  8.       userResult.success(userDb) 
  9.     } 
  10.   } 

這些回調(diào)可以使用coroutines轉(zhuǎn)換為順序的函數(shù)調(diào)用。

  1. suspend fun loginUser(userId: String, password: String): User { 
  2.   val user = userRemoteDataSource.logUserIn(userId, password
  3.   val userDb = userLocalDataSource.logUserIn(user
  4.   return userDb 

在coroutines代碼中,我們給函數(shù)添加了suspend修飾符。這將告訴編譯器,這個(gè)函數(shù)需要在一個(gè)coroutine內(nèi)執(zhí)行。作為一個(gè)開發(fā)者,你可以把suspend函數(shù)看作是一個(gè)普通的函數(shù),但它的執(zhí)行可能被掛起,并在某個(gè)時(shí)候恢復(fù)。

簡(jiǎn)而言之,suspend就是一種編譯器生成的回調(diào)。

與回調(diào)不同的是,coroutines提供了一種在線程之間切換和處理異常的簡(jiǎn)單方法。

但是,當(dāng)我們把函數(shù)標(biāo)記為suspend時(shí),編譯器實(shí)際上在幕后做了什么?

Suspend到底做了什么

回到loginUser的suspend函數(shù),注意它調(diào)用的其他函數(shù)也是suspend函數(shù)。

  1. suspend fun loginUser(userId: String, password: String): User { 
  2.   val user = userRemoteDataSource.logUserIn(userId, password
  3.   val userDb = userLocalDataSource.logUserIn(user
  4.   return userDb 
  5. // UserRemoteDataSource.kt 
  6. suspend fun logUserIn(userId: String, password: String): User 
  7. // UserLocalDataSource.kt 
  8. suspend fun logUserIn(userId: String): UserDb 

簡(jiǎn)而言之,Kotlin編譯器將使用有限狀態(tài)機(jī)(我們將在后面介紹)把suspend函數(shù)轉(zhuǎn)換為優(yōu)化版本的回調(diào)實(shí)現(xiàn)。你說對(duì)了,編譯器會(huì)幫你寫這些回調(diào),它們的本質(zhì),依然是回調(diào)!

Continuation的真面目

suspend函數(shù)之間的通信方式是使用Continuation對(duì)象。一個(gè)Continuation只是一個(gè)帶有一些額外信息的通用回調(diào)接口。正如我們稍后將看到的,它將代表一個(gè)suspend函數(shù)的生成狀態(tài)機(jī)。

讓我們看一下它的定義。

  1. interface Continuation<in T> { 
  2.   public val context: CoroutineContext 
  3.   public fun resumeWith(value: Result<T>) 

context是在continuation中使用的CoroutineContext。

resumeWith用一個(gè)Result來恢復(fù)Coroutine的執(zhí)行,這個(gè)Result可以包含一個(gè)導(dǎo)致suspend的計(jì)算結(jié)果的值或者是一個(gè)異常。

注意:從Kotlin 1.3開始,你還可以使用擴(kuò)展函數(shù)resume(value: T)和resumeWithException(exception: Throwable),它們是resumeWith調(diào)用的特殊版本。

編譯器將使用函數(shù)簽名中的額外參數(shù)completion(Continuation類型)替換suspend修飾符,該參數(shù)將用于將suspend函數(shù)的結(jié)果傳達(dá)給調(diào)用它的coroutine。

  1. fun loginUser(userId: String, password: String, completion: Continuation<Any?>) { 
  2.   val user = userRemoteDataSource.logUserIn(userId, password
  3.   val userDb = userLocalDataSource.logUserIn(user
  4.   completion.resume(userDb) 

為了簡(jiǎn)單起見,我們的例子將返回Unit而不是User。User對(duì)象將在添加的Continuation參數(shù)中被 "返回"。

suspend函數(shù)的字節(jié)碼實(shí)際上返回 Any? 因?yàn)樗?(T | COROUTINE_SUSPENDED)的聯(lián)合類型。這允許函數(shù)在可以時(shí)同步返回。

注意:如果你用suspend修飾符標(biāo)記一個(gè)不調(diào)用其他suspend函數(shù)的函數(shù),編譯器也會(huì)添加額外的Continuation參數(shù),但不會(huì)對(duì)它做任何事情,函數(shù)體的字節(jié)碼看起來就像一個(gè)普通函數(shù)。

你也可以在其他地方看到Continuation接口。

當(dāng)使用suspendCoroutine或suspendCancellableCoroutine將基于回調(diào)的API轉(zhuǎn)換為coroutine時(shí)(你應(yīng)該總是傾向于使用這種方法),你直接與Continuation對(duì)象交互,以恢復(fù)在運(yùn)行時(shí)被suspend的作為參數(shù)傳遞的代碼塊。

你可以使用suspend函數(shù)上的startCoroutine擴(kuò)展函數(shù)來啟動(dòng)一個(gè)coroutine。它接收一個(gè)Continuation對(duì)象作為參數(shù),當(dāng)新的coroutine完成時(shí),無論是結(jié)果還是異常,都會(huì)被調(diào)用。

切換不同的Dispatchers

你可以在不同的Dispatchers之間進(jìn)行交換,在不同的線程上執(zhí)行計(jì)算。那么Kotlin如何知道在哪里恢復(fù)一個(gè)暫停的計(jì)算?

Continuation有一個(gè)子類型,叫做DispatchedContinuation,它的resume函數(shù)可以對(duì)CoroutineContext中可用的Dispatcher進(jìn)行調(diào)度調(diào)用。除了Dispatchers.Unconfined的isDispatchNeeded函數(shù)覆蓋(在dispatch之前調(diào)用)總是返回false,所有Dispatcher都會(huì)調(diào)用dispatch。

在協(xié)程中,有個(gè)不成文的約定,那就是,suspend函數(shù)默認(rèn)是不阻塞線程的,也就是說,suspend函數(shù)的調(diào)用者,不用為suspend函數(shù)運(yùn)行在哪個(gè)線程而擔(dān)心,suspend函數(shù)會(huì)自己處理它工作的線程,不大部分時(shí)候,都是通過withContext來進(jìn)行切換的。

生成狀態(tài)機(jī)

免責(zé)聲明:文章其余部分所展示的代碼將不完全符合編譯器所生成的字節(jié)碼。它將是足夠準(zhǔn)確的Kotlin代碼,使你能夠理解內(nèi)部真正發(fā)生的事情。這種表示法是由Coroutines 1.3.3版本生成的,在該庫的未來版本中可能會(huì)發(fā)生變化。

Kotlin編譯器將識(shí)別函數(shù)何時(shí)可以在內(nèi)部suspend。每個(gè)suspend point都將被表示為有限狀態(tài)機(jī)中的一個(gè)狀態(tài)。這些狀態(tài)由編譯器用標(biāo)簽表示,前面示例中的suspend函數(shù)在編譯后,會(huì)產(chǎn)生類似下面的偽代碼。

  1. fun loginUser(userId: String, password: String, completion: Continuation<Any?>) { 
  2.   // Label 0 -> first execution 
  3.   val user = userRemoteDataSource.logUserIn(userId, password
  4.   // Label 1 -> resumes from userRemoteDataSource 
  5.   val userDb = userLocalDataSource.logUserIn(user
  6.   // Label 2 -> resumes from userLocalDataSource 
  7.   completion.resume(userDb) 

為了更好地表示狀態(tài)機(jī),編譯器將使用一個(gè)when語句來實(shí)現(xiàn)不同的狀態(tài)。

  1. fun loginUser(userId: String, password: String, completion: Continuation<Any?>) { 
  2.   when(label) { 
  3.     0 -> { // Label 0 -> first execution 
  4.         userRemoteDataSource.logUserIn(userId, password
  5.     } 
  6.     1 -> { // Label 1 -> resumes from userRemoteDataSource 
  7.         userLocalDataSource.logUserIn(user
  8.     } 
  9.     2 -> { // Label 2 -> resumes from userLocalDataSource 
  10.         completion.resume(userDb) 
  11.     } 
  12.     else -> throw IllegalStateException(...) 
  13.   } 

編譯器將suspend函數(shù)編譯成帶有Continuation參數(shù)的方法叫做CPS(Continuation-Passing-Style)變換。

這段代碼是不完整的,因?yàn)椴煌臓顟B(tài)沒有辦法分享信息。編譯器會(huì)在函數(shù)中使用相同的Continuation對(duì)象來做這件事。這就是為什么Continuation的泛型是Any? 而不是原始函數(shù)的返回類型(即User)。

此外,編譯器將創(chuàng)建一個(gè)私有類,1)持有所需的數(shù)據(jù),2)遞歸地調(diào)用loginUser函數(shù)以恢復(fù)執(zhí)行。你可以看看下面這個(gè)生成的類的近似值。

免責(zé)聲明:注釋不是由編譯器生成的。我添加它們是為了解釋它們的作用,并使跟隨代碼更容易理解。

  1. fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) { 
  2.   class LoginUserStateMachine( 
  3.     // completion parameter is the callback to the function  
  4.     // that called loginUser 
  5.     completion: Continuation<Any?> 
  6.   ): CoroutineImpl(completion) { 
  7.     // Local variables of the suspend function 
  8.     var userUser? = null 
  9.     var userDb: UserDb? = null 
  10.     // Common objects for all CoroutineImpls 
  11.     var result: Any? = null 
  12.     var label: Int = 0 
  13.     // this function calls the loginUser again to trigger the 
  14.     // state machine (label will be already in the next state) and 
  15.     // result will be the result of the previous state's computation 
  16.     override fun invokeSuspend(result: Any?) { 
  17.       this.result = result 
  18.       loginUser(nullnull, this) 
  19.     } 
  20.   } 
  21.   ... 

由于invokeSuspend將僅用Continuation對(duì)象的信息來再次調(diào)用loginUser,loginUser函數(shù)簽名中的其余參數(shù)都變成了空值。在這一點(diǎn)上,編譯器只需要添加如何在狀態(tài)之間轉(zhuǎn)移的信息。

它需要做的第一件事是知道1)這是函數(shù)第一次被調(diào)用,或者2)函數(shù)已經(jīng)從之前的狀態(tài)恢復(fù)。它通過檢查傳入的continuation是否是LoginUserStateMachine類型來實(shí)現(xiàn)。

  1. fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) { 
  2.   ... 
  3.   val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion) 
  4.   ... 

如果是第一次,它將創(chuàng)建一個(gè)新的LoginUserStateMachine實(shí)例,并將收到的完成實(shí)例作為一個(gè)參數(shù)存儲(chǔ)起來,這樣它就能記住如何恢復(fù)調(diào)用這個(gè)實(shí)例的函數(shù)。如果不是這樣,它將只是繼續(xù)執(zhí)行狀態(tài)機(jī)(suspend函數(shù))。

現(xiàn)在,讓我們看看編譯器為在狀態(tài)間移動(dòng)和在狀態(tài)間共享信息而生成的代碼。

  1. /* Copyright 2019 Google LLC.  
  2.    SPDX-License-Identifier: Apache-2.0 */ 
  3. fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) { 
  4.     ... 
  5.  
  6.     val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion) 
  7.  
  8.     when(continuation.label) { 
  9.         0 -> { 
  10.             // Checks for failures 
  11.             throwOnFailure(continuation.result) 
  12.             // Next time this continuation is called, it should go to state 1 
  13.             continuation.label = 1 
  14.             // The continuation object is passed to logUserIn to resume  
  15.             // this state machine's execution when it finishes 
  16.             userRemoteDataSource.logUserIn(userId!!, password!!, continuation) 
  17.         } 
  18.         1 -> { 
  19.             // Checks for failures 
  20.             throwOnFailure(continuation.result) 
  21.             // Gets the result of the previous state 
  22.             continuation.user = continuation.result as User 
  23.             // Next time this continuation is called, it should go to state 2 
  24.             continuation.label = 2 
  25.             // The continuation object is passed to logUserIn to resume  
  26.             // this state machine's execution when it finishes 
  27.             userLocalDataSource.logUserIn(continuation.user, continuation) 
  28.         } 
  29.         ... // leaving out the last state on purpose 
  30.     } 

花點(diǎn)時(shí)間瀏覽一下上面的代碼,看看你是否能發(fā)現(xiàn)與前面的代碼片斷的不同之處。讓我們看看編譯器生成了什么。

  • when語句的參數(shù)是LoginUserStateMachine實(shí)例中的Label。
  • 每次處理一個(gè)新的狀態(tài)時(shí),都會(huì)有一個(gè)檢查,以防這個(gè)函數(shù)suspend時(shí)發(fā)生異常。
  • 在調(diào)用下一個(gè)suspend函數(shù)(即logUserIn)之前,LoginUserStateMachine實(shí)例的Label將被更新為下一個(gè)狀態(tài)。
  • 當(dāng)在這個(gè)狀態(tài)機(jī)內(nèi)部有一個(gè)對(duì)另一個(gè)suspend函數(shù)的調(diào)用時(shí),continuation的實(shí)例(LoginUserStateMachine類型)被作為一個(gè)參數(shù)傳遞。要調(diào)用的suspend函數(shù)也已經(jīng)被編譯器轉(zhuǎn)化了,它是另一個(gè)像這樣的狀態(tài)機(jī),它把一個(gè)continuation對(duì)象也作為參數(shù)!當(dāng)那個(gè)suspend函數(shù)的狀態(tài)機(jī)完成后,它將恢復(fù)這個(gè)狀態(tài)機(jī)的執(zhí)行。

最后一個(gè)狀態(tài)是不同的,因?yàn)樗仨毣謴?fù)調(diào)用這個(gè)函數(shù)的執(zhí)行,正如你在代碼中看到的,它對(duì)存儲(chǔ)在LoginUserStateMachine中的cont變量(在構(gòu)造時(shí))調(diào)用resume。

  1. /* Copyright 2019 Google LLC.  
  2.    SPDX-License-Identifier: Apache-2.0 */ 
  3. fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) { 
  4.     ... 
  5.  
  6.     val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion) 
  7.  
  8.     when(continuation.label) { 
  9.         ... 
  10.         2 -> { 
  11.             // Checks for failures 
  12.             throwOnFailure(continuation.result) 
  13.             // Gets the result of the previous state 
  14.             continuation.userDb = continuation.result as UserDb 
  15.             // Resumes the execution of the function that called this one 
  16.             continuation.cont.resume(continuation.userDb) 
  17.         } 
  18.         else -> throw IllegalStateException(...) 
  19.     } 

正如你所看到的,Kotlin編譯器為我們做了很多事情!從這個(gè)suspend函數(shù)功能來舉例。

  1. suspend fun loginUser(userId: String, password: String): User { 
  2.   val user = userRemoteDataSource.logUserIn(userId, password
  3.   val userDb = userLocalDataSource.logUserIn(user
  4.   return userDb 

編譯器為我們生成了下面這一切。

  1. /* Copyright 2019 Google LLC.  
  2.    SPDX-License-Identifier: Apache-2.0 */ 
  3. fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) { 
  4.  
  5.     class LoginUserStateMachine( 
  6.         // completion parameter is the callback to the function that called loginUser 
  7.         completion: Continuation<Any?> 
  8.     ): CoroutineImpl(completion) { 
  9.         // objects to store across the suspend function 
  10.         var userUser? = null 
  11.         var userDb: UserDb? = null 
  12.  
  13.         // Common objects for all CoroutineImpl 
  14.         var result: Any? = null 
  15.         var label: Int = 0 
  16.  
  17.         // this function calls the loginUser again to trigger the  
  18.         // state machine (label will be already in the next state) and  
  19.         // result will be the result of the previous state's computation 
  20.         override fun invokeSuspend(result: Any?) { 
  21.             this.result = result 
  22.             loginUser(nullnull, this) 
  23.         } 
  24.     } 
  25.  
  26.     val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion) 
  27.  
  28.     when(continuation.label) { 
  29.         0 -> { 
  30.             // Checks for failures 
  31.             throwOnFailure(continuation.result) 
  32.             // Next time this continuation is called, it should go to state 1 
  33.             continuation.label = 1 
  34.             // The continuation object is passed to logUserIn to resume  
  35.             // this state machine's execution when it finishes 
  36.             userRemoteDataSource.logUserIn(userId!!, password!!, continuation) 
  37.         } 
  38.         1 -> { 
  39.             // Checks for failures 
  40.             throwOnFailure(continuation.result) 
  41.             // Gets the result of the previous state 
  42.             continuation.user = continuation.result as User 
  43.             // Next time this continuation is called, it should go to state 2 
  44.             continuation.label = 2 
  45.             // The continuation object is passed to logUserIn to resume  
  46.             // this state machine's execution when it finishes 
  47.             userLocalDataSource.logUserIn(continuation.user, continuation) 
  48.         } 
  49.         2 -> { 
  50.             // Checks for failures 
  51.             throwOnFailure(continuation.result) 
  52.             // Gets the result of the previous state 
  53.             continuation.userDb = continuation.result as UserDb 
  54.             // Resumes the execution of the function that called this one 
  55.             continuation.cont.resume(continuation.userDb) 
  56.         } 
  57.         else -> throw IllegalStateException(...) 
  58.     } 

Kotlin編譯器將每個(gè)suspend函數(shù)轉(zhuǎn)化為一個(gè)狀態(tài)機(jī),在每次函數(shù)需要suspend時(shí)使用回調(diào)進(jìn)行優(yōu)化。

現(xiàn)在你知道了編譯器在編譯時(shí)到底做了什么,你就可以更好地理解為什么一個(gè)suspend函數(shù)在它執(zhí)行完所有工作之前不會(huì)返回。另外,你也會(huì)知道,代碼是如何在不阻塞線程的情況下進(jìn)行suspend的——這是因?yàn)?,?dāng)函數(shù)恢復(fù)時(shí)需要執(zhí)行的信息被存儲(chǔ)在Continuation對(duì)象中!

向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 專注 Android-Kotlin-Flutter 歡迎大家訪問

 

責(zé)任編輯:武曉燕 來源: 群英傳
相關(guān)推薦

2020-11-24 08:05:18

5G衛(wèi)星通信

2022-02-23 08:18:06

nginx前端location

2021-01-31 10:52:42

Http 協(xié)議高并發(fā)

2022-08-21 10:26:31

PyCharmPython

2021-11-11 11:27:55

大數(shù)據(jù)分析系統(tǒng)

2021-12-28 20:05:19

數(shù)字交通信息

2020-07-14 09:01:19

PGMySQLPostgreSQL

2022-01-07 17:28:07

操作系統(tǒng)IO 內(nèi)存

2024-02-07 12:35:00

React并發(fā)模式concurrent

2013-05-29 10:17:56

Hadoop分布式文件系統(tǒng)

2021-03-08 08:03:44

注解Spring配置

2021-05-11 07:30:58

JNIJavaAPI

2022-05-04 08:38:32

Netty網(wǎng)絡(luò)框架

2020-07-20 10:20:30

this前端代碼

2021-01-28 17:41:32

Github網(wǎng)站Pull Reques

2022-04-10 19:26:07

TypeScript類型語法

2022-04-15 08:54:39

PythonAsync代碼

2024-07-12 15:08:23

Python@wraps函數(shù)

2020-04-16 12:04:09

5G基站4G
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)