---
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 中標示圖案  |
| `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")
}
```
> 
### 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}")
}
```
> 
:::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)
}
```
> 
## 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 創建的執行序(請觀察執行順序)
>
> 
### 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` 更加的可控
> 
* 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()
}
}
```
> 
:::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}")
}
```
> 
:::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 後被取消,但是實際上還是有輸出
> 
### 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)
}
```
> 
## 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`")
}
}
```
> 
### 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()
}
}
```
> 從結果中我們可以看到,尚未碰到暫停點時,它就運行在呼叫者的執行序,當碰到暫停點後,就跑去其他執行序運作!
>
> 
## 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`)
> 
### 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` 同步 !
:::
> 
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 被關閉後,所有相關的任務都會被關閉
:::
> 
## 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` 是捕捉不到**!
> 
### 異常傳遞:父攜程接收
* 這裡有個要注意的點,就是「**協程的異常是會傳遞的**」,**子協程發生異常會向父協程拋出,++子協程自身並捕捉不到++**
```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")
}
```
> 
### 捕捉 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")
}
```
> 
### 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")
}
}
}
}
```
> 
* **`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")
}
}
}
}
```
> 
### 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")
}
```
> 
必須手動捕捉其錯誤! 雖然可以捕捉,但 **發生錯誤之後該協程就不會再繼續後面的任務了**
```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")
}
```
> 
## 更多的 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`