--- title: 'Kotlin Coroutine 協程' disqus: kyleAlien --- Kotlin Coroutine 協程 === ## Overview of Content :::success * 如果喜歡讀更好看一點的網頁版本,可以到我新做的網站 [**DevTech Ascendancy Hub**](https://devtechascendancy.com/) 本篇文章對應的是 [**應用 Kotlin 協程:對比 Thread、創建協程、任務掛起 | Dispatcher、CoroutineContext、CoroutineScope**](https://devtechascendancy.com/applied-kotlin-coroutines-in-depth-guide/) ::: [TOC] ## Coroutine 對比 Thread 機制 協程 `Coroutine` 是輕量級的執行序,**它並不會綁定特定執行序**(有可能掛起前在 A Thread, 恢復時轉為 B Thread 上下文) :::info * `Coroutine` 與一般 `Thread` 比起來: 1. **普通 Thread 是透過 CPU 發出中斷信號時做上下文切換,而 Coroutine 則是靠程式自身實現**(通常會有 Library 可以用),不需要 CPU 控制(跟硬體沒關係) 2. 普通 Thread 使用「堵塞」機制,Coroutine 使用「掛起」機制 | 機制 | 行為特色 | CPU 使用概念 | | - | - | - | | 堵塞 | 讓出 CPU 時間,並在滿足條件後「被動喚醒」,如果條件不滿足就一直堵塞;`Thread#wait()` 概念 | **釋放 CPU** | | 掛起 | 讓出 CPU 時間,並在一定時間後「主動喚醒」去檢查條件是否滿足 | **仍佔用 CPU** | ::: ### Kotlin Coroutine 重點類、重點概念 * **Kotlin Coroutine 重點抽象類** | Coroutine 相關類 | 說明 | 補充 | | -------- | -------- | -------- | | `CoroutineDispather` | **協程調度器**;決定 Coroutine 內的任務要在哪個 Thread 中運行! | - | | `CoroutineContext` | **協程上下文**;所有 Corotine 都要在 CoroutineContext 範圍內 | 包含一個默認的 CoroutineDispather | | `CoroutineScope` | **協程作用域**;CoroutineScope 會在具有生命週期上的實體實現(GlobalScope 則是 top-level 是整個程式的生命週期) | 它包含了一個 CoroutineContext | ```mermaid graph TB; subgraph CoroutineScope subgraph CoroutineContext CoroutineDispather end end ``` * **Kotlin Coroutine 任務管理類** | Coroutine 任務管理類 | 說明 | 補充 | | -------- | -------- | -------- | | `Job` | 任務執行的過程被封裝成 Job,之後這個 Job 會交由 CoroutineDispather 執行 | Job 具有簡單的生命週期,可被執行 & 取消 ...,其中也有父子類關係,父 Job 可以管控子 Job | | `Deferred` | **Job 的拓展類**;可以讓 Job 有返回值 | Deferred 拓展 Job | * **Kotlin Coroutine 概念 & 關鍵字** | Coroutine 概念 & 關鍵字 | 說明 | 補充 | | -------- | -------- | -------- | | `suspend` (關鍵字) | 表明該函數被執行時會被掛起!(掛起,但不是放 CPU) | suspend 關鍵字只能被使用在 CoroutineContext | | `suspend point` (概念) | 每個掛起的函數都稱為 suspend point | IDE 中標示圖案 ![](https://i.imgur.com/fHkgI5t.png) | | `Continuation`(概念) | 兩個 suspend point 之間都稱為 Continuation | 兩個 suspend point 之間的程式是運行在外部的 `CoroutineScope` | ```mermaid graph TB; subgraph 最外層 CoroutineScope subgraph suspend point 1 執行掛起行為_1 end Continuation subgraph suspend point 2 執行掛起行為_2 end end ``` ## Kotlin Coroutine 創建 Coroutine 的創建有多種方式,以下我們就來介紹解個 Coroutine 創建的方式 ### launch 函數:啟動新 CoroutineScope * **`launch` 創建 Coroutine**:它創建一個 CoroutineScope 當作上下文,將其賦予最後一個 Lambda 參數,再 **返回 Job 對象**(可透過 Job 來管理協程任務) ```kotlin= fun main() { // 返回 Job val job : Job = GlobalScope.launch { // 上下文為 CoroutineScope println("Global with Job - start") delay(1000) println("Global with Job - end") } Thread.sleep(500) job.cancel() // 直接取消協程 println("Main finish") } ``` > ![](https://i.imgur.com/8UmslTe.png) ### async 函數:創建可延遲啟動的協程 * **`async` 創建 Coroutine**:它同樣會創建 CoroutineScope 賦予最後一個 Lambda 參數作為上下文,再 **返回 Deferred 對象** 而它與 `launch` 函數的不同點在於,`launch` 在呼叫時就會啟動協程,而 `async` 可以在需要的時候再啟動協程 ```kotlin= suspend fun main() { // await 需配合 suspend 使用 // 返回 Job val deferred = GlobalScope.async { // 上下文為 CoroutineScope println("Global with Job - start") delay(1000) println("Global with Job - end") } // 故意延遲 500ms delay(500) val startStamp = System.currentTimeMillis() deferred.await() // 等待協程 (需要 suspend) // deferred.cancel() // 可取消 println("Main finish: use time: ${System.currentTimeMillis() - startStamp}") } ``` > ![](https://i.imgur.com/VYk3OpY.png) :::warning * `runBlocking` 函數:runBlocking 會堵塞當前 Thread 直到任務結束,**較多使用在測試上** ::: ### 延遲啟動 Coroutine * **Coroutine 預設在創建後會自動啟動任務**;這其中可以透過 `start` 參數(`launch`, `async` 都可以)來控制 Coroutine 的啟動方式 > 以下為 `launch` 源碼 ```kotlin= public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, // 預設使用 CoroutineStart.DEFAULT start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit ): Job { val newContext = newCoroutineContext(context) val coroutine = if (start.isLazy) LazyStandaloneCoroutine(newContext, block) else StandaloneCoroutine(newContext, active = true) coroutine.start(start, coroutine, block) return coroutine } ``` * 我們可以透過設定 start 參數為 `CoroutineStart.LAZY` 可以達到 **懶加載功能**(它就不會自動啟動),之後只有在呼叫 `start`、`join`、`await` 後才會開始任務 ```kotlin= fun defaultStart() { val startTime = System.currentTimeMillis() val job = GlobalScope.launch(start = CoroutineStart.LAZY) { println("defaultStart - launch - start: ${System.currentTimeMillis() - startTime}") delay(1000) println("defaultStart - launch - finish: ${System.currentTimeMillis() - startTime}") } Thread.sleep(1000) println("Call start: ${System.currentTimeMillis() - startTime}") job.start() } fun main() { defaultStart() Thread.sleep(5000) } ``` > ![](https://i.imgur.com/9xLWKsT.png) ## Coroutine 掛起函數 Kotlin 中使用 `suspend` 關鍵字來標記一個掛起函數,在該函數執行完之前,不會執行下一行程式 (類似一種「**同步**」操作) :::info **Kotlin 的掛起函數是採用 「CPS, `Continuation Passing Style`」 與「狀態機」實現,保證會執行完掛起函數後,再往下執行** ::: ### delay 函數:狀態暫停 * `delay` 函數會將當前 CoroutineScope 中的 **狀態進行暫停**,它類似於 Thread#sleep 方法,但 **`delay` 它其實暫停的是協程不是執行序**(仍尚未放開 CPU 的使用權!) 以下我們透過 `launch` 函數來啟動一個新的 CoroutineScope,來觀察 delay 到底會不會暫停 Thread(同時我們也可以觀察到 `launch` 會不創建一個新的執行序) ```kotlin= fun main() = runBlocking { fun printThreadInfo(order: Int) = println("$order. current thread: ${Thread.currentThread().name}") printThreadInfo(1) GlobalScope.launch { // 內外不同 Thread! 所以不會被暫停 printThreadInfo(3) delay(1000) printThreadInfo(4) } printThreadInfo(2) Thread.sleep(2000) printThreadInfo(5) } ``` > 從下圖結果中,我們可以看到 `launch` 創建了一個新執行序,並且 `delay` 函數並不會影響到外部執行序,而是影響到 Coroutine 創建的執行序(請觀察執行順序) > > ![](https://i.imgur.com/lHU0JKj.png) ### yield 讓出執行資源 * 在 Java 中 `Thread#yield` 方法代表的意思是讓當前 Thread 讓出資源,並給其他 Thread 進行搶奪這個資源的使用權 (有可能其他 Thread 或自身搶到) > `Thread#yied` 通常使用的較少,因為它的可控性較低 * Koltin 中 `yield` 函數也是差不多的意思,不過差異點在於:**協程讓出資源後會將當前協程分發到 CoroutineDispatcher 的對列中做等待,等其他協程執行完才會執行自身協程**(也就是讓出,並向後排隊的概念) 以下開啟兩個協程來互讓 (`yield`) 資源,觀察它們讓出後排序的行為,是否會跟 `Thread#yield` 一樣隨機亂序 ```kotlin= fun main() { runBlocking { fun printThreadInfo(order: Int) = println("$order. current thread: ${Thread.currentThread().name}") val job1 = launch { printThreadInfo(1) yield() printThreadInfo(3) yield() printThreadInfo(5) } val job2 = launch { printThreadInfo(2) yield() printThreadInfo(4) yield() printThreadInfo(6) } printThreadInfo(0) job1.join() job2.join() } } ``` 從下圖解果中,我們可以看出協程的 `yield` 更加的可控 > ![](https://i.imgur.com/xdRvsKU.png) * CoroutineScope 的重要性、特性:CoroutineScope 會有關到協程的執行影響範圍;我們將上面的例子稍加修改,用不同的 CoroutineScope 去呼叫 `yield`,我們可以觀察到 `yield` 就不會等待 (因為上下文已經不同) ```kotlin= fun main() { runBlocking { fun printThreadInfo(order: Int) = println("$order. current thread: ${Thread.currentThread().name}") val job1 = GlobalScope.launch { // 啟動新 CoroutineScope printThreadInfo(1) yield() printThreadInfo(3) yield() printThreadInfo(5) } val job2 = GlobalScope.launch { // 啟動新 CoroutineScope printThreadInfo(2) yield() printThreadInfo(4) yield() printThreadInfo(6) } printThreadInfo(0) job1.join() job2.join() } } ``` > ![](https://i.imgur.com/hi5GSRi.png) :::warning * **runBlocking 內的 `launch` 會繼承上層的 CoroutineScope**,所以輸出的就是 Thread 就是 MainThread (因為內部的協程在同一個 CoroutineContext 中) ::: ### withContext:等待指定的協程、NonCancelable * **協程中的 `withContext` 用於切換該協程要在哪個 ContextDispatcher 中運行**,並且它也是一個掛起操作 (它也可以返回一個值,如同 runBlocking) ```kotlin= fun main() : Unit = runBlocking { launch { println("In launch scope: ${Thread.currentThread().name}") withContext(Dispatchers.Default) { // 切換 Dispatcher println("withContext(Default): ${Thread.currentThread().name}") } println("---") withContext(Dispatchers.Unconfined) { println("withContext(Unconfined): ${Thread.currentThread().name}") } } println("Outer scope: ${Thread.currentThread().name}") } ``` > ![](https://i.imgur.com/jZi7JFS.png) :::success * **不同的 `Dispatcher` 差異** | Dispatcher 種類 | 啟動協程的 Thread | 執行協程時使用的 Thread | | -------- | -------- | -------- | | `Default` | 被調用的 Thread | **協程的公用執行序池** | | `IO` | 被調用的 Thread | **協程的 IO 密集操作執行序池** | | `Unconfined` | 被調用的 Thread | 默認運行在當前協程的執行序,但如果碰到第一個暫停點(`suspend point`)後,它會運行在 **任意執行序中** | ::: * `withContext` 預設是可以取消的,但我們也可以配合 **使用 `NonCancelable` (它是一個 CoroutineContext) 來讓該協程不可被取消** ```kotlin= fun main() : Unit = runBlocking { val job = launch { println("In launch scope: ${Thread.currentThread().name}") // 加入 NonCancelable withContext(Dispatchers.Default + NonCancellable) { // 切換 Dispatcher delay(200) println("withContext(Default): ${Thread.currentThread().name}") } } delay(100) job.cancelAndJoin() // 將 cancel 立刻加入任務 println("Outer scope: ${Thread.currentThread().name}") } ``` 可以看到任務在 100ms 後被取消,但是實際上還是有輸出 > ![](https://i.imgur.com/VIH8Kwc.png) ### coroutineScope 關鍵字 * **`coroutineScope` 同樣採用父協程的 CoroutineContext**,並且它與 `withContext` 不同,**它無法設定其他的 CoroutineDispatcher** ```kotlin= fun main() { fun printThreadInfo() = println("current thread: ${Thread.currentThread().name}") GlobalScope.launch { val res1 = withContext(Dispatchers.Default) { printThreadInfo() delay(100) 10 } val res2 = coroutineScope { printThreadInfo() delay(100) 20 } println("res: ${res1 + res2}") } Thread.sleep(1_000) } ``` > ![](https://i.imgur.com/P3dfoG7.png) ## Coroutine Dispatcher **調度器代表的是該協程任務會在哪個執行序中運行** ### 切換 Coroutine 調度器 * 以下創建多個不同的協程,並設定不同調度器,在觀察其運行時是使用哪個執行序執行 ```kotlin= fun main() : Unit = runBlocking { fun threadInfo(msg: String) = println("$msg, working in ${Thread.currentThread().name}") // 使用父協程設定 launch { threadInfo("`Default`") } launch(coroutineContext) { threadInfo("`Parent Context`") } // 使用預設 Dispatcher launch(Dispatchers.Unconfined) { threadInfo("`Unconfined`") } launch(Dispatchers.IO) { threadInfo("`IO`") } launch(Dispatchers.Default) { threadInfo("`Default`") } // 自己創建 Thread (也可以使用 newFixedThreadPoolContext) launch(newSingleThreadContext("Custom_Thread")) { threadInfo("`Custom Thread`") } } ``` > ![](https://i.imgur.com/qFQtn5D.png) ### Unconfined 特性:碰到暫停我就換 * Coroutine 預設有提供我們以下幾種 Dispatcher | Dispatcher 種類 | 啟動協程的 Thread | 執行協程時使用的 Thread | | -------- | -------- | -------- | | `Default` | 被調用的 Thread | **協程的公用執行序池** | | `IO` | 被調用的 Thread | **協程的 IO 密集操作執行序池** | | `Unconfined` | 被調用的 Thread | 默認運行在當前協程的執行序,但如果碰到第一個暫停點(`suspend point`)後,它會運行在 **任意執行序中** | 其中,`Unconfined` 是的特性比較特別,所以我們這邊來測試一下 `Unconfined` 的情況 ```kotlin= fun main() : Unit = runBlocking { fun threadInfo() = println("Working in ${Thread.currentThread().name}") launch (Dispatchers.Unconfined) { // 預設與啟動 Dispatcher 的相同 threadInfo() // 直到遇到第一個 suspend 函數 delay(300) // 切換任意 Thread 運行 threadInfo() } } ``` > 從結果中我們可以看到,尚未碰到暫停點時,它就運行在呼叫者的執行序,當碰到暫停點後,就跑去其他執行序運作! > > ![](https://i.imgur.com/a05yHxs.png) ## CoroutineContext ### 父子協程 - 關係 * 可以透過 CoroutineContext 來「**區分協程之間的關係**」,**只有在相同 CoroutineContext 中的協程才會有父子關係**… 範例如下 ```kotlin= fun main() { val parentContext = GlobalScope.launch { // 使用外層的 CoroutineContext val childContext = launch(coroutineContext) { println("My context same as parent - start.") delay(1000) println("My context same as parent - end.") // 沒有輸出 } // 創建一個新的 CoroutineContext val diffContext = GlobalScope.launch { println("I have own context - start.") delay(1000) println("I have own context - end.") // 正常輸出 } childContext.join() diffContext.join() } Thread.sleep(500) parentContext.cancel() Thread.sleep(1500) } ``` 從下圖結果中我們可以看出 * 相同 CoroutineContext 之下的程式,會等待同一個 CoroutineContext 下的任務都執行完成 (`parentContext` 會等待 `childContext`) * 不同的 CoroutineContext 則不會等待(`parentContext` 不會等待 `diffContext`) > ![](https://i.imgur.com/faoN5V6.png) ### MultiCoroutineContext:讓 Coroutine 產生關聯 * 協程支援 MultiCoroutineContext,可以透過 `+` 符號來添加 CoroutineContext (有複寫這個 Operator);透過 MultiCoroutineContext 可以讓無關係的 CoroutineContext 產生關係 1. 首先我們先看兩個完全不同的 CoroutineContext 執行的結果,看看兩者是否會有連結關係; 如果兩個 Coroutine 有連結關係的話,會等待內部 Coroutine 否則則不會等待 ```kotlin= fun main() { // 外部 Coroutine val parentContext = GlobalScope.launch { // 內部 Coroutine val diffContext = GlobalScope.launch(coroutineContext) { println("I have own context - start.") delay(1000) println("I have own context - end.") // 不輸出 } diffContext.join() } Thread.sleep(500) parentContext.cancel() Thread.sleep(1500) } ``` :::info 可以觀察到無關係的 `diffContext` 生命週期與 `parentContext` 同步 ! ::: > ![reference link](https://i.imgur.com/dBq3892.png) 2. 再來,我們透過 MultiCoroutineContext 的方式,將多個 Coroutine 產生生關聯 以下,透過創建一個 Job 來管理多個不同的 CoroutineContext (同樣透過 `+` 號添加) ```kotlin= fun main() { val mrgJob = Job() GlobalScope.launch(Dispatchers.Default + mrgJob) { delay(100) println("Dispatchers.Default + mrgJob") } GlobalScope.launch(Dispatchers.IO + mrgJob) { delay(200) println("Dispatchers.IO + mrgJob") } GlobalScope.launch(Dispatchers.Unconfined + mrgJob) { delay(300) println("Dispatchers.Unconfined + mrgJob") // 不會執行到 } Thread.sleep(200) // 中途關閉 Coroutine mrgJob.cancel() Thread.sleep(500) } ``` :::info 只要管理的 Job 被關閉後,所有相關的任務都會被關閉 ::: > ![](https://i.imgur.com/TqoNL4A.png) ## CoroutineScope 協程作用域 上面使用到的 `launch`、`async` 都是 **`CoroutineScope` 的拓展函數**;而 `GlobalScope` 則是 CoroutineScope 的實現類 :::danger * `GlobalScope` 是 top-level 函數,所以沒有綁定 Job 對象,其生命週期跟整個應用程式存亡 ::: ### CoroutineScope 發生異常 * 首先我們先啟動一個 Coroutine,並在內部拋出異常,並且外部用 `try/catch` 包裹,來觀察是否可以抓取到異常 ```kotlin= // 以下程式會拋出異常 fun failToCatch() { try { GlobalScope.launch { delay(100) throw Exception("inner coroutine throw exception") } } catch (e : Exception) { println("Catch Exception Success") } } fun main() = runBlocking { failToCatch() delay(1000) println("Main finish") } ``` 下圖中,我們可以看到在 Coroutine 中拋出異常,可以發現 **外部的 `try/catch` 是捕捉不到**! > ![](https://i.imgur.com/1moQ8sI.png) ### 異常傳遞:父攜程接收 * 這裡有個要注意的點,就是「**協程的異常是會傳遞的**」,**子協程發生異常會向父協程拋出,++子協程自身並捕捉不到++** ```kotlin= fun failToCatchChild() { val handler = CoroutineExceptionHandler { context, throwable -> println("get exception: $throwable") } GlobalScope.launch { delay(100) launch(handler) { // 自身捕捉不到異常 throw Exception("inner coroutine throw exception of child") } } } fun main() = runBlocking { failToCatchChild() delay(1000) println("Main finish") } ``` > ![](https://i.imgur.com/wgWGBzO.png) ### 捕捉 CoroutineScope 異常:CoroutineExceptionHandler * 要捕捉協程中的異常可以使用 `CoroutineExceptionHandler`,將它設定成 CoroutineContext,就可以捕捉協程異常 ```java= fun successToCatch() { val handler = CoroutineExceptionHandler { context, throwable -> println("get exception: $throwable") } GlobalScope.launch(handler) { delay(100) throw Exception("inner coroutine throw exception") } } fun main() = runBlocking { successToCatch() delay(1000) println("Main finish") } ``` > ![](https://i.imgur.com/VTBlZrP.png) ### supervisorScope 自己捕捉異常 * coroutineScope 它不能捕捉到子協程的異常 ```kotlin= fun catchSupervisorChild1() { val handler = CoroutineExceptionHandler { context, throwable -> println("get exception: $throwable") } GlobalScope.launch/*(handler)*/ { // 放在這裡可以捕捉到 delay(100) coroutineScope { launch(handler) { // 無法捕捉 throw Exception("inner coroutine throw exception of supervisorScope's child") } } } } ``` > ![](https://i.imgur.com/V6SIWh8.png) * **`supervisorScope` 比較特別,它會將異常「交由子協程自己處理」,所以這時候子協程才能捕捉到異常** ```kotlin= fun catchSupervisorChild2() { val handler = CoroutineExceptionHandler { context, throwable -> println("get exception: $throwable") } GlobalScope.launch { delay(100) supervisorScope { // supervisorScope 子協程需要自己處理異常 launch(handler) { throw Exception("inner coroutine throw exception of supervisorScope's child") } } } } ``` > ![](https://i.imgur.com/X8tNRsT.png) ### CoroutineExceptionHandler 無法捕捉 async / withContext * 如果協程使用 async / withContext,而在其中拋出錯誤,`CoroutineExceptionHandler` 是無法捕捉到的,必須自己用 Try/Catch 包裹 ```kotlin= fun main() = runBlocking { val handler = CoroutineExceptionHandler { _, e -> if(e is Exception) { println("Throwable is exception: $e") } println("Get exception: ${e.message}") } val superJob = CoroutineScope(SupervisorJob() + handler) // coroutine 中的任務 “可以” 繼續下去 superJob.launch { delay(100) println("Job - 1") } superJob.launch { delay(200) println("Job - 2") } // superJob.async { // CoroutineExceptionHandler 無法捕捉 async 拋出的錯誤 // delay(250) // println("Job - 2.5") // throw Exception("Throw it from job 2.5") // }.await() withContext(superJob.coroutineContext) {// CoroutineExceptionHandler 無法捕捉 withContext 拋出的錯誤 delay(270) println("Job - 2.7") throw Exception("Throw it from job 2.7") } // ------------------------------------ 這下面都收不到 superJob.launch { delay(300) println("Job - 3") } println("Main finish") } ``` > ![](https://i.imgur.com/nAqc19F.png) 必須手動捕捉其錯誤! 雖然可以捕捉,但 **發生錯誤之後該協程就不會再繼續後面的任務了** ```kotlin= try { withContext(superJob.coroutineContext) {// CoroutineExceptionHandler 無法捕捉 withContext 拋出的錯誤 delay(270) println("Job - 2.7") throw Exception("Throw it from job 2.7") } } catch (e :Exception) { println("withContext occur exception. $e") } ``` > ![](https://i.imgur.com/Tb6cSby.png) ## 更多的 Kotlin 語言相關文章 在這裡,我們提供了一系列豐富且深入的 Kotlin 語言相關文章,涵蓋了從基礎到進階的各個方面。讓我們一起來探索這些精彩內容! ### Kotlin 語言基礎 * **Kotlin 語言基礎**:想要建立堅實的 Kotlin 基礎?以下這些文章將帶你深入探索 Kotlin 的關鍵基礎和概念,幫你打造更堅固的 Kotlin 語言基礎 :::info * [**Kotlin 函數、類、屬性 | DataClass、Sealed、Object 關鍵字 | Enum、Companion、NPE**](https://devtechascendancy.com/kotlin-functions_oop_dataclass_sealed_object/) * [**深入探究 Kotlin 與 Java 泛型:擦除、取得泛型類型、型變、投影 | 協變、逆變**](https://devtechascendancy.com/explore-kotlin-java-generics_type_erasure/) * [**深入 Kotlin 函數特性:Inline、擴展、標準函數全解析 | 提升程式碼效能與可讀性**](https://devtechascendancy.com/kotlin_inline_extensions_standards-func/) * [**Kotlin DSL、操作符、中綴表達式 Infix | DSL 詳解 | DSL 設計與應用**](https://devtechascendancy.com/kotlin-dsl-operators-infix-explained/) ::: ### Kotlin 特性、特點 * **Kotlin 特性、特點**:探索 Kotlin 的獨特特性和功能,加深對 Kotlin 語言的理解,並增強對於語言特性的應用 :::warning * [**Kotlin 代理與懶加載機制:使用、lazy 深度解析**](https://devtechascendancy.com/kotlin-delegate_java-proxy_lateinit_lazy/) * [**Kotlin Lambda 編程 & Bytecode | Array & Collections 集合 | 集合函數式 API**](https://devtechascendancy.com/kotlin-lambda-bytecode-array-collections-functional/) * [**深入理解 Kotlin:智能推斷與 Contact 規則**](https://devtechascendancy.com/kotlin-smart-inference-contract-rules-guide/) ::: ### Kotlin 進階:協程、響應式、異步 * **Kotlin 進階:協程、響應式、異步**:若想深入學習 Kotlin 的進階主題,包括協程應用、Channel 使用、以及 Flow 的探索,請查看以下文章 :::danger * [**應用 Kotlin 協程:對比 Thread、創建協程、任務掛起 | Dispatcher、CoroutineContext、CoroutineScope**](https://devtechascendancy.com/applied-kotlin-coroutines-in-depth-guide/) * [**Kotlin Channel 使用介紹 | Select、Actor | 生產者消費者**](https://devtechascendancy.com/kotlin-channel_select_actor_cs/) * [**探索 Kotlin Flow:基本使用、RxJava 對比、背壓機制 | Flow 細節**](https://devtechascendancy.com/kotlin-flow-usage_compare-rx_backpressure/) ::: ## Appendix & FAQ :::info https://juejin.cn/post/7087222504170717192 ::: ###### tags: `Kotlin`