<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                [TOC] # 協程 ## 什么是協程? **`Kotlin`協程的核心競爭力在于:它能簡化異步并發任務,以同步方式寫異步代碼** 這也是為什么要引入協程的原因了:簡化異步并發任務 ## 協程與線程的區別是什么? 協程基于線程,但相對于線程輕量很多,可理解為在用戶層模擬線程操作; 每創建一個協程,都有一個內核態線程動態綁定,用戶態下實現調度、切換,真正執行任務的還是內核線程。 線程的上下文切換都需要內核參與,而協程的上下文切換,完全由用戶去控制,避免了大量的中斷參與,減少了線程上下文切換與調度消耗的資源。 線程是操作系統層面的概念,協程是語言層面的概念 **線程與協程最大的區別在于:線程是被動掛起恢復,協程是主動掛起恢復** ## `Kotlin`中的協程是什么? "假"協程,`Kotlin`在語言級別并沒有實現一種同步機制(鎖),還是依靠`Kotlin-JVM`的提供的`Java`關鍵字(如`synchronized`),即鎖的實現還是交給線程處理 因而`Kotlin`協程本質上只是一套基于原生`Java線程池` 的封裝。 `Kotlin` 協程的核心競爭力在于:它能簡化異步并發任務,以同步方式寫異步代碼。 # 協程要點 suspend 上面的代碼之所以能寫成類似`同步`的方式,關鍵還是在于那三個請求函數的定義。與普通函數不同的地方在于,它們都被 `suspend` 修飾,這代表它們都是:`掛起函數`。 ~~~kotlin // delay(1000L)用于模擬網絡請求 //掛起函數 // ↓ suspend fun getUserInfo(): String { withContext(Dispatchers.IO) { delay(1000L) } return "BoyCoder" } //掛起函數 // ↓ suspend fun getFriendList(user: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "Tom, Jack" } //掛起函數 // ↓ suspend fun getFeedList(list: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "{FeedList..}" } 復制代碼 ~~~ 那么,掛起函數到底是什么? ## 掛起函數 掛起函數(Suspending Function),從字面上理解,就是`可以被掛起的函數`。suspend 有:掛起,`暫停`的意思。在這個語境下,也有點暫停的意思。暫停更容易被理解,但掛起更準確。 掛起函數,能被**掛起**,當然也能**恢復**,他們一般是成對出現的。 我們來看看掛起函數的執行流程,注意動畫當中出現的`閃爍`,這代表正在請求網絡。 **一定要多看幾遍,確保沒有遺漏其中的細節。** ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/60453cfebece44779b6581aefef14284~tplv-k3u1fbpfcp-watermark.awebp) 從上面的動畫,我們能知道: * 表面上看起來是同步的代碼,實際上也涉及到了線程切換。 * 一行代碼,切換了兩個線程。 * `=`左邊:主線程 * `=`右邊:IO線程 * 每一次從`主線程`到`IO線程`,都是一次協程`掛起`(suspend) * 每一次從`IO線程`到`主線程`,都是一次協程`恢復`(resume)。 * 掛起和恢復,這是掛起函數特有的能力,普通函數是不具備的。 * 掛起,只是將程序執行流程轉移到了其他線程,主線程并未被阻塞。 * 如果以上代碼運行在 Android 系統,我們的 App 是仍然可以響應用戶的操作的,主線程并不繁忙,這也很容易理解。 掛起函數的執行流程我們已經很清楚了,那么,Kotlin 協程到底是如何做到`一行代碼切換兩個線程`的? 這一切的`魔法`都藏在了掛起函數的`suspend`關鍵字里。 # suspend原理 `CPS`與`狀態機`就是協程實現的核心 1. 增加了`Continuation`類型的參數 (callback 返回結果) 2. 返回類型從`String`轉變成了`Any`(返回是否被掛起) 3. `continuation.label` 是狀態流轉的關鍵,`label`改變一次代表協程發生了一次掛起恢復 4. 我們寫在協程里的代碼,被拆分到狀態機里各個狀態中,分開執行 ## CPS 轉化 下面用動畫演示掛起函數在 `CPS` 轉換過程中,函數簽名的變化: ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7d8acc3656434f7da8fb9b6699c2f7ff~tplv-k3u1fbpfcp-watermark.awebp) 可以看出主要有兩點變化 1.增加了`Continuation`類型的參數 2.返回類型從`String`轉變成了`Any` 參數的變化我們之前講過,為什么返回值要變呢? ### 掛起函數返回值 掛起函數經過 `CPS` 轉換后,它的返回值有一個重要作用:標志該掛起函數有沒有被掛起。 聽起來有點奇怪,掛起函數還會不掛起嗎? > 只要被`suspend`修飾的函數都是掛起函數,但是不是所有掛起函數都會被掛起 > 只有當掛起函數里包含異步操作時,它才會被真正掛起 由于 `suspend` 修飾的函數,既可能返回 `CoroutineSingletons.COROUTINE_SUSPENDED`,表示掛起 也可能返回同步運行的結果,甚至可能返回 null 為了適配所有的可能性,`CPS` 轉換后的函數返回值類型就只能是 `Any?`了。 ## 狀態機 `kotlin`協程的實現依賴于狀態機 想要查看其實現,可以將`kotin`源碼反編譯成字節碼來查看編譯后的代碼 關于字節碼的分析之前已經有很多人做過了,而且做的很好,可參考:[Kotlin Jetpack 實戰 | 09. 圖解協程原理](https://juejin.cn/post/6883652600462327821#heading-14 "https://juejin.cn/post/6883652600462327821#heading-14") 讀者可通過上面的鏈接進行詳細的學習,下面給出狀態機的動畫演示 ![](https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/56ba74c4febf4140a26174eac73e1880~tplv-k3u1fbpfcp-watermark.awebp) 1. 協程實現的核心就是`CPS`變換與狀態機 2. 協程執行到掛起函數,一個函數如果被掛起了,它的返回值會是:`CoroutineSingletons.COROUTINE_SUSPENDED` 3. 掛起函數執行完成后,通過`Continuation.resume`方法回調,這里的`Continuation`是通過`CPS`傳入的 4. 傳入的`Continuation`實際上是`ContinuationImpl`,`resume`方法最后會再次回到`invokeSuspend`方法中 5. `invokeSuspend`方法即是我們寫的代碼執行的地方,在協程運行過程中會執行多次 6. `invokeSuspend`中通過狀態機實現狀態的流轉 7. `continuation.label` 是狀態流轉的關鍵,`label`改變一次代表協程發生了一次掛起恢復 8. 通過`break label`實現`goTo`的跳轉效果 9. 我們寫在協程里的代碼,被拆分到狀態機里各個狀態中,分開執行 10. 每次協程切換后,都會檢查是否發生異常 11. 切換協程之前,狀態機會把之前的結果以成員變量的方式保存在 `continuation` 中。 以上是狀態機流轉的大概流程,讀者可跟著參考鏈接,過一下編譯后的字節碼執行流程后,再來判斷這個流程是否正確 # 協程怎么進行線程切換 簡單來講主要包括以下步驟: 1.向`CoroutineContext`添加`Dispatcher`,指定運行的協程 2.在啟動時將`suspend block`創建成`Continuation`,并調用`intercepted`生成`DispatchedContinuation` 3.`DispatchedContinuation`就是對原有協程的裝飾,在這里調用`Dispatcher`完成線程切換任務后,`resume`被裝飾的協程,就會執行協程體內的代碼了 **其實`kotlin`協程就是用裝飾器模式實現線程切換的** # Flow `Flow` 就是 `Kotlin` 協程與響應式編程模型結合的產物,你會發現它與 `RxJava` 非常像,二者之間也有相互轉換的 `API`,使用起來非常方便。 `Flow`有以下特點: 1.冷數據流,不消費則不生產,這一點與`Channel`正相反:`Channel`的發送端并不依賴于接收端。 2.`Flow`通過`flowOn`改變數據發射的線程,數據消費線程則由協程所在線程決定 3.與`RxJava`類似,支持通過`catch`捕獲異常,通過`onCompletion` 回調完成 4.`Flow`沒有提供取消方法,可以通過取消`Flow`所在協程的方式來取消 ## `Flow`為什么是個冷流? 冷流即開始消費時才生產數據,不消費則不生產,我們來看下源碼 先看下`flow{}`中發生了什么 ~~~kotlin public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block) // Named anonymous object private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() { override suspend fun collectSafely(collector: FlowCollector<T>) { collector.block() } } 復制代碼 ~~~ 可以看出,`flow{}`中做的事也很簡單,主要就是創建了一個繼承自`AbstractFlow`的`SafeFlow` 再來看下`AbstractFlow`中的內容 ~~~kotlin public abstract class AbstractFlow<T> : Flow<T> { @InternalCoroutinesApi public final override suspend fun collect(collector: FlowCollector<T>) { // 1. collector 做一層包裝 val safeCollector = SafeCollector(collector, coroutineContext) try { // 2. 處理數據接收者 collectSafely(safeCollector) } finally { // 3. 釋放協程相關的參數 safeCollector.releaseIntercepted() } } // collectSafely 方法應當遵循以下的約束 // 1. 不應當在collectSafely方法里面切換線程,比如 withContext(Dispatchers.IO) // 2. collectSafely 默認不是線程安全的 public abstract suspend fun collectSafely(collector: FlowCollector<T>) } private class SafeFlow<T>(private val block: suspend FlowCollector<T>.() -> Unit) : AbstractFlow<T>() { override suspend fun collectSafely(collector: FlowCollector<T>) { collector.block() } } 復制代碼 ~~~ 發現主要做了三件事: 1.對數據接收方`FlowCollector` 做了一層包裝,也就是這個`SafeCollector` 2.調用它里面的抽象方法`AbstractFlow#collectSafely` 方法。 3.釋放協程的一些信息。 結合以下之前看的`SafeFlow`,它實現了`AbstractFlow#collectSafely`方法,調用了`collector.block()`,也就是運行了`flow{}`塊中的代碼。 現在就很清晰了,為什么`Flow`是冷流? **因為它會在每一次`collect`的時候才會去觸發發送數據的動作** ## `Flow`是怎么切換線程的 `Flow`切換線程的方式與協程切換線程是類似的 都是通過啟動一個子協程,然后通過`CoroutineContext`中的`Dispatchers`切換線程 不同的地方在于`Flow`切換過程中利用了`Channel`來傳遞數據 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/15008f844469448095030c65310298a0~tplv-k3u1fbpfcp-watermark.awebp) 由于`Flow`切換線程的源碼過多,就不在這里綴述了,有興趣的同學可以跟一下源碼,詳情可見:[flowOn()如何做到切換協程](https://juejin.cn/post/6914802148614242312#heading-9 "https://juejin.cn/post/6914802148614242312#heading-9") # 協程異常處理 ## CoroutineExceptionHandler * “ CoroutineExceptionHandler是用于全局“全部捕獲”行為的最后手段。 您無法從CoroutineExceptionHandler中的異常中恢復。 當調用處理程序時,協程已經完成,并帶有相應的異常。 通常,處理程序用于記錄異常,顯示某種錯誤消息,終止和/或重新啟動應用程序。 * 為了使CoroutineExceptionHandler起作用,必須將其設置在CoroutineScope或頂級協程中。 * 如果需要在代碼的特定部分處理異常,建議在協程內部的相應代碼周圍使用try / catch。 這樣,您可以防止協程異常完成(現在已捕獲異常),重試該操作和/或采取其他任意操作: # 異常的傳播機制 本文主要分析了`kotlin`協程的異常傳播機制,主要分為以下幾步 1. 協程體內拋出異常 2. 判斷是否是`CancellationException`,如果是則不做處理 3. 判斷父協程是否為空或為`supervisorScope`,如果是則調用`handleJobException`,處理異常 4. 如果不是則將異常傳遞給父協程,然后父協程再進行一遍上面的流程 以上步驟總結為流程圖如下所示: ![](https://img.kancloud.cn/78/75/78756f781744055004dfb38daacc76cc_648x839.png) # 參考資料 [全民 Kotlin:協程特別篇](https://mp.weixin.qq.com/s/xqAdliU4g0cV1oIwwwYJlA) [【帶著問題學】協程到底是什么?](https://juejin.cn/post/6973650934664527885) [Kotlin Jetpack 實戰 | 09. 圖解協程原理](https://juejin.cn/post/6883652600462327821) [協程異常機制與優雅封裝 | 技術點評](https://juejin.cn/post/6935472332735512606)
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看