kyle shanks
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee

      This note has no invitees

    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Note Insights New
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Note Insights Versions and GitHub Sync Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee

    This note has no invitees

  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       Owned this note    Owned this note      
    Published Linked with GitHub
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    --- 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`

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully