主頁 > 移動端開發 > [Kotlin Tutorials 22] 協程中的例外處理

[Kotlin Tutorials 22] 協程中的例外處理

2023-06-08 09:45:50 移動端開發

協程中的例外處理

coroutine exception handling

Parent-Child關系

如果一個coroutine拋出了例外, 它將會把這個exception向上拋給它的parent, 它的parent會做以下三件事情:

  • 取消其他所有的children.
  • 取消自己.
  • 把exception繼續向上傳遞.

這是默認的例外處理關系, 取消是雙向的, child會取消parent, parent會取消所有child.

catch不住的exception

看這個代碼片段:

fun main() {
    val scope = CoroutineScope(Job())
    try {
        scope.launch {
            throw RuntimeException()
        }
    } catch (e: Exception) {
        println("Caught: $e")
    }

    Thread.sleep(100)
}

這里的例外catch不住了.
會直接讓main函式的主行程崩掉.

這是因為和普通的例外處理機制不同, coroutine中未被處理的例外并不是直接拋出, 而是按照job hierarchy向上傳遞給parent.

如果把try放在launch里面還行.

默認的例外處理

默認情況下, child發生例外, parent和其他child也會被取消.

fun main() {
    println("start")
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val scope = CoroutineScope(Job() + exceptionHandler)

    scope.launch {
        println("child 1")
        delay(1000)
        println("finish child 1")
    }.invokeOnCompletion { throwable ->
        if (throwable is CancellationException) {
            println("Coroutine 1 got cancelled!")
        }
    }

    scope.launch {
        println("child 2")
        delay(100)
        println("child 2 throws exception")
        throw RuntimeException()
    }

    Thread.sleep(2000)
    println("end")
}

列印出:

start
child 1
child 2
child 2 throws exception
Coroutine 1 got cancelled!
CoroutineExceptionHandler got java.lang.RuntimeException
end

SupervisorJob

如果有一些情形, 開啟了多個child job, 但是卻不想因為其中一個的失敗而取消其他, 怎么辦? 用SupervisorJob.

比如:

val uiScope = CoroutineScope(SupervisorJob())

如果你用的是scope builder, 那么用supervisorScope.

SupervisorJob改造上面的例子:

fun main() {
    println("start")
    val exceptionHandler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val scope = CoroutineScope(SupervisorJob() + exceptionHandler)

    scope.launch {
        println("child 1")
        delay(1000)
        println("finish child 1")
    }.invokeOnCompletion { throwable ->
        if (throwable is CancellationException) {
            println("Coroutine 1 got cancelled!")
        }
    }

    scope.launch {
        println("child 2")
        delay(100)
        println("child 2 throws exception")
        throw RuntimeException()
    }
    Thread.sleep(2000)
    println("end")
}

輸出:

start
child 1
child 2
child 2 throws exception
CoroutineExceptionHandler got java.lang.RuntimeException
finish child 1
end

盡管coroutine 2拋出了例外, 另一個coroutine還是做完了自己的作業.

SupervisorJob的特點

SupervisorJob把取消變成了單向的, 只能從上到下傳遞, 只能parent取消child, 反之不能取消.
這樣既顧及到了由于生命周期的結束而需要的正常取消, 又避免了由于單個的child失敗而取消所有.

viewModelScope的context就是用了SupervisorJob() + Dispatchers.Main.immediate.

除了把取消變為單向的, supervisorScope也會和coroutineScope一樣等待所有child執行結束.

supervisorScope中直接啟動的coroutine是頂級coroutine.
頂級coroutine的特性:

  • 可以加exception handler.
  • 自己處理exception.
    比如上面的例子中coroutine child 2可以直接加exception handler.

使用注意事項, SupervisorJob只有兩種寫法:

  • 作為CoroutineScope的引數傳入: CoroutineScope(SupervisorJob()).
  • 使用supervisorScope方法.

把Job作為coroutine builder(比如launch)的引數傳入是錯誤的做法, 不起作用, 因為一個新的coroutine總會assign一個新的Job.

例外處理的辦法

try-catch

和普通的例外處理一樣, 我們可以用try-catch, 只是注意要在coroutine里面:

fun main() {
    val scope = CoroutineScope(Job())
    scope.launch {
        try {
            throw RuntimeException()
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }

    Thread.sleep(100)
}

這樣就能列印出:

Caught: java.lang.RuntimeException

對于launch, try要包住整塊.
對于async, try要包住await陳述句.

scope function: coroutineScope()

coroutineScope會把其中未處理的exception拋出來.

相比較于這段代碼中catch不到的exception:

fun main() {
    val scope = CoroutineScope(Job())
    scope.launch {
        try {
            launch {
                throw RuntimeException()
            }
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }
    Thread.sleep(100)
}

沒走到catch里, 仍然是主行程崩潰.

這個exception是可以catch到的:

fun main() {
    val scope = CoroutineScope(Job())
    scope.launch {
        try {
            coroutineScope {
                launch {
                    throw RuntimeException()
                }
            }
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }

    Thread.sleep(100)
}

列印出:

Caught: java.lang.RuntimeException

因為這里coroutineScope把例外又重新拋出來了.

注意這里換成supervisorScope可是不行的.

CoroutineExceptionHandler

CoroutineExceptionHandler是例外處理的最后一個機制, 此時coroutine已經結束了, 在這里的處理通常是報告log, 展示錯誤等.
如果不加exception handler那么unhandled exception會進一步往外拋, 如果最后都沒人處理, 那么可能造成行程崩潰.

CoroutineExceptionHandler需要加在root coroutine上.

這是因為child coroutines會把例外處理代理到它們的parent, 后者繼續代理到自己的parent, 一直到root.
所以對于非root的coroutine來說, 即便指定了CoroutineExceptionHandler也沒有用, 因為例外不會傳到它.

兩個例外:

  • async的例外在Deferred物件中, CoroutineExceptionHandler也沒有任何作用.
  • supervision scope下的coroutine不會向上傳遞exception, 所以CoroutineExceptionHandler不用加在root上, 每個coroutine都可以加, 單獨處理.

通過這個例子可以看出另一個特性: CoroutineExceptionHandler只有當所有child都結束之后才會處理例外資訊.

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception -> 
        println("CoroutineExceptionHandler got $exception") 
    }
    val job = GlobalScope.launch(handler) {
        launch { // the first child
            try {
                delay(Long.MAX_VALUE)
            } finally {
                withContext(NonCancellable) {
                    println("Children are cancelled, but exception is not handled until all children terminate")
                    delay(100)
                    println("The first child finished its non cancellable block")
                }
            }
        }
        launch { // the second child
            delay(10)
            println("Second child throws an exception")
            throw ArithmeticException()
        }
    }
    job.join()
}

輸出:

Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
The first child finished its non cancellable block
CoroutineExceptionHandler got java.lang.ArithmeticException

如果多個child都拋出例外, 只有第一個被handler處理, 其他都在exception.suppressed欄位里.

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
    }
    val job = GlobalScope.launch(handler) {
        launch {
            try {
                delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
            } finally {
                throw ArithmeticException() // the second exception
            }
        }
        launch {
            delay(100)
            throw IOException() // the first exception
        }
        delay(Long.MAX_VALUE)
    }
    job.join()
}

輸出:

CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]

單獨說一下async

async比較特殊:

  • 作為top coroutine時, 在await的時候try-catch例外.
  • 如果是非top coroutine, async塊里的例外會被立即拋出.

例子:

fun main() {
    val scope = CoroutineScope(SupervisorJob())
    val deferred = scope.async {
        throw RuntimeException("RuntimeException in async coroutine")
    }

    scope.launch {
        try {
            deferred.await()
        } catch (e: Exception) {
            println("Caught: $e")
        }
    }

    Thread.sleep(100)
}

這里由于用了SupervisorJob, 所以async是top coroutine.

fun main() {

    val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, exception ->
        println("Handle $exception in CoroutineExceptionHandler")
    }

    val topLevelScope = CoroutineScope(SupervisorJob() + coroutineExceptionHandler)
    topLevelScope.launch {
        async {
            throw RuntimeException("RuntimeException in async coroutine")
        }
    }
    Thread.sleep(100)
}

當它不是top coroutine時, 例外會被直接拋出.

特殊的CancellationException

CancellationException是特殊的exception, 會被例外處理機制忽略, 即便拋出也不會向上傳遞, 所以不會取消它的parent.
但是CancellationException不能被catch, 如果它不被拋出, 其實協程沒有被成功cancel, 還會繼續執行.

CancellationException的透明特性:
如果CancellationException是由內部的其他例外引起的, 它會向上傳遞, 并且把原始的那個例外傳遞上去.

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, exception ->
        println("CoroutineExceptionHandler got $exception")
    }
    val job = GlobalScope.launch(handler) {
        val inner = launch { // all this stack of coroutines will get cancelled
            launch {
                launch {
                    throw IOException() // the original exception
                }
            }
        }
        try {
            inner.join()
        } catch (e: CancellationException) {
            println("Rethrowing CancellationException with original cause")
            throw e // cancellation exception is rethrown, yet the original IOException gets to the handler
        }
    }
    job.join()
}

輸出:

Rethrowing CancellationException with original cause
CoroutineExceptionHandler got java.io.IOException

這里Handler拿到的是最原始的IOException.

Further Reading

官方檔案:

  • Coroutine exceptions handling

Android官方檔案上鏈接的博客和視頻:

  • Exceptions in coroutines
  • KotlinConf 2019: Coroutines! Gotta catch 'em all! by Florina Muntenescu & Manuel Vivo

其他:

  • Kotlin Coroutines and Flow - Use Cases on Android
  • Why exception handling with Kotlin Coroutines is so hard and how to successfully master it!
作者: 圣騎士Wind
出處: 博客園: 圣騎士Wind
Github: https://github.com/mengdd
微信公眾號: 圣騎士Wind
微信公眾號: 圣騎士Wind

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/554651.html

標籤:其他

上一篇:[Kotlin Tutorials 21] 協程的取消

下一篇:返回列表

標籤雲
其他(160605) Python(38215) JavaScript(25484) Java(18209) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7238) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5873) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4588) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2403) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1983) 功能(1967) HtmlCss(1955) Web開發(1951) C++(1933) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1880) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • [Kotlin Tutorials 22] 協程中的例外處理

    # 協程中的例外處理 ![coroutine exception handling](https://img2023.cnblogs.com/blog/325852/202306/325852-20230608084235670-684439238.png) ## Parent-Child關系 如果 ......

    uj5u.com 2023-06-08 09:45:50 more
  • [Kotlin Tutorials 21] 協程的取消

    # 協程的取消 本文討論協程的取消, 以及實作時可能會碰到的幾個問題. ![coroutine cancellation](https://img2023.cnblogs.com/blog/325852/202306/325852-20230607235812812-279507376.png) 本 ......

    uj5u.com 2023-06-08 09:45:40 more
  • 怎樣將PWA和小程式融合?

    PWA代表“漸進式網路應用”(Progressive Web Application)。它是一種結合了網頁和移動應用程式功能的技術概念。PWA旨在提供類似于原生應用程式的用戶體驗,包括離線訪問、推送通知、后臺同步等功能,同時又具有網頁的優勢,如跨平臺、無需下載安裝等。 PWA使用現代的Web技術來創 ......

    uj5u.com 2023-06-08 09:45:33 more
  • 盤點| 三種移動跨平臺方案

    跨平臺技術是前端人必備技能,今天就來為大家解讀一下近幾年業界主流的三大移動端跨平臺方案: Web 天然跨平臺: Web App、PWA(Progressive Web Apps)、Hybrid App、PHA(Progress Hybrid App)都可以實作跨平臺,WebView 是一種基于瀏覽器 ......

    uj5u.com 2023-06-08 09:45:30 more
  • 怎樣將PWA和小程式融合?

    PWA代表“漸進式網路應用”(Progressive Web Application)。它是一種結合了網頁和移動應用程式功能的技術概念。PWA旨在提供類似于原生應用程式的用戶體驗,包括離線訪問、推送通知、后臺同步等功能,同時又具有網頁的優勢,如跨平臺、無需下載安裝等。 PWA使用現代的Web技術來創 ......

    uj5u.com 2023-06-08 09:45:18 more
  • [Kotlin Tutorials 22] 協程中的例外處理

    # 協程中的例外處理 ![coroutine exception handling](https://img2023.cnblogs.com/blog/325852/202306/325852-20230608084235670-684439238.png) ## Parent-Child關系 如果 ......

    uj5u.com 2023-06-08 09:45:02 more
  • [Kotlin Tutorials 21] 協程的取消

    # 協程的取消 本文討論協程的取消, 以及實作時可能會碰到的幾個問題. ![coroutine cancellation](https://img2023.cnblogs.com/blog/325852/202306/325852-20230607235812812-279507376.png) 本 ......

    uj5u.com 2023-06-08 09:44:52 more
  • 盤點| 三種移動跨平臺方案

    跨平臺技術是前端人必備技能,今天就來為大家解讀一下近幾年業界主流的三大移動端跨平臺方案: Web 天然跨平臺: Web App、PWA(Progressive Web Apps)、Hybrid App、PHA(Progress Hybrid App)都可以實作跨平臺,WebView 是一種基于瀏覽器 ......

    uj5u.com 2023-06-08 09:44:45 more
  • 大型 3D 互動開發和優化實踐

    我們團隊接到了食品頻道的一個互動專案的開發需求,希望通過 3D 場景的展示和互動方式,作為對未來購物的一種嘗試與探索,滿足用戶對未來美好新奇的一個需求。將購物場景化、娛樂化,給用戶帶來美好的購物感受。 ......

    uj5u.com 2023-06-06 13:29:43 more
  • From Java To Kotlin 2:Kotlin 型別系統與泛型

    上期主要分享了 From Java To Kotlin 1 :空安全、擴展、函式、Lambda。

    這是 From Java to Kotlin 第二期。
    From Java to Kotlin 關鍵在于 **思維的轉變**。 ......

    uj5u.com 2023-06-06 13:29:25 more