[Kotlin Tutorials 21] 協程的取消

来源:https://www.cnblogs.com/mengdd/archive/2023/06/08/kotlin-coroutine-cancellation.html
-Advertisement-
Play Games

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


協程的取消

本文討論協程的取消, 以及實現時可能會碰到的幾個問題.

coroutine cancellation

本文屬於合輯: https://github.com/mengdd/KotlinTutorials

協程的取消

取消的意義: 避免資源浪費, 以及多餘操作帶來的問題.

基本特性:

  • cancel scope的時候會cancel其中的所有child coroutines.
  • 一旦取消一個scope, 你將不能再在其中launch新的coroutine.
  • 一個在取消狀態的coroutine是不能suspend的.

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

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

Android開發中的取消

在Android開發中, 比較常見的情形是由於View生命周期的終止, 我們需要取消一些操作.

通常我們不需要手動調用cancel()方法, 那是因為我們利用了一些更高級的包裝方法, 比如:

  • viewModelScope: 會在ViewModel onClear的時候cancel.
  • lifecycleScope: 會在作為Lifecycle Owner的View對象: Activity, Fragment到達DESTROYED狀態時cancel.

取消並不是自動獲得的

all suspend functions from kotlinx.coroutines are cancellable, but not yours.

kotlin官方提供的suspend方法都會有cancel的處理, 但是我們自己寫的suspend方法就需要自己留意.
尤其是耗時或者帶迴圈的地方, 通常需要自己加入檢查, 否則即便調用了cancel, 代碼也繼續在執行.

有這麼幾種方法:

  • isActive()
  • ensureActive()
  • yield(): 除了ensureActive以外, 會出讓資源, 比如其他工作不需要再往線程池裡加線程.

一個在迴圈中檢查coroutine是否依然活躍的例子:

fun main() = runBlocking {
    val startTime = currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // cancellable computation loop
            // print a message twice a second
            if (currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}

輸出:

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

catch Exception和runCatching

眾所周知catch一個很general的Exception類型可能不是一個好做法.
因為你以為捕獲了A, B, C異常, 結果實際上還有D, E, F.

捕獲具體的異常類型, 在開發階段的快速失敗會幫助我們更早定位和解決問題.

協程還推出了一個"方便"的runCatching方法, catchThrowable.
讓我們寫出了看似更"保險", 但卻更容易破壞取消機制的代碼.

如果我們catch了CancellationException, 會破壞Structured Concurrency.
看這個例子:

fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        println("my long time function start")
        myLongTimeFunction()
        println("my other operations ==== ") // this line should not be printed when cancelled
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}

private suspend fun myLongTimeFunction() = runCatching {
    var i = 0
    while (i < 10) {
        // print a message twice a second
        println("job: I'm sleeping ${i++} ...")
        delay(500)
    }
}

輸出:

my long time function start
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
my other operations ==== 
main: Now I can quit.

當job cancel了以後後續的工作不應該繼續進行, 然而我們可以看到log仍然被列印出來, 這是因為runCatching把異常全都catch了.

這裡有個open issue討論這個問題: https://github.com/Kotlin/kotlinx.coroutines/issues/1814

CancellationException的特殊處理

如何解決上面的問題呢? 基本方案是把CancellationException再throw出來.

比如對於runCatching的改造, NowInAndroid里有這麼一個方法suspendRunCatching:

private suspend fun <T> suspendRunCatching(block: suspend () -> T): Result<T> = try {
    Result.success(block())
} catch (cancellationException: CancellationException) {
    throw cancellationException
} catch (exception: Exception) {
    Log.i(
        "suspendRunCatching",
        "Failed to evaluate a suspendRunCatchingBlock. Returning failure Result",
        exception
    )
    Result.failure(exception)
}

上面的例子改為用這個suspendRunCatching方法替代runCatching就修好了.

上面例子的輸出變為:

my long time function start
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

不想取消的處理

可能還有一些工作我們不想隨著job的取消而完全取消.

資源清理工作

finally通常用於try block之後的的資源清理, 如果其中沒有suspend方法那麼沒有問題.

如果finally中的代碼是suspend的, 如前所述, 一個在取消狀態的coroutine是不能suspend的.
那麼需要用一個withContext(NonCancellable).

例子:

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            withContext(NonCancellable) {
                println("job: I'm running finally")
                delay(1000L)
                println("job: And I've just delayed for 1 sec because I'm non-cancellable")
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
}

註意這個方法一般用於會suspend的資源清理, 不建議在各個場合到處使用, 因為它破壞了對coroutine執行取消的控制.

需要更長生命周期的工作

如果有一些工作需要比View/ViewModel更長的生命周期, 可以把它放在更下層, 用一個生命周期更長的scope.
可以根據不同的場景設計, 比如可以用一個application生命周期的scope:

class MyApplication : Application() {
  // No need to cancel this scope as it'll be torn down with the process
  val applicationScope = CoroutineScope(SupervisorJob() + otherConfig)
}

再把這個scope註入到repository中去.

如果需要做的工作比application的生命周期更長, 那麼可以考慮用WorkManager.

總結: 不要破壞Structured Concurrency

Structure Concurrency為開發者提供了方便管理多個coroutines的有效方法.
基本上破壞Structure Concurrency特性的行為(比如用GlobalScope, 用NonCancellable, catch CancellationException等)都是反模式, 要小心使用.

還要註意不要隨便傳遞job.
CoroutineContext有一個元素是job, 但是這並不意味著我們可以像切Dispatcher一樣隨便傳一個job參數進去.
文章: Structured Concurrency Anniversary

看這裡: https://github.com/Kotlin/kotlinx.coroutines/issues/1001

References & Further Reading

Kotlin官方文檔的網頁版和markdown版本:

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

其他:

作者: 聖騎士Wind
出處: 博客園: 聖騎士Wind
Github: https://github.com/mengdd
微信公眾號: 聖騎士Wind
微信公眾號: 聖騎士Wind
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 摘要:GaussDB(DWS)查詢過濾器(黑名單)提供查詢過濾功能,支持自動隔離反覆被終止的查詢,防止爛SQL再次執行。 本文分享自華為雲社區《GaussDB(DWS)查詢過濾器原理與應用》,作者:門前一棵葡萄樹 。 一、概述 GaussDB(DWS)查詢過濾器(黑名單)提供查詢過濾功能,支持自動隔 ...
  • 啟動安裝程式 下載sqlserver2014,雙擊startup.exe進行安裝 系統配置檢查器 使用系統配置檢查器,看系統是否符合安裝sqlserver2014的所有要求 開始安裝 然後點擊安裝,全新sqlserver獨立安裝或向現有安裝添加功能 安裝規則 然後就是使用預設的設置,點開詳細信息,可 ...
  • 前段時間,[袋鼠雲離線開發產品](https://www.dtstack.com/dtinsight?src=szsm)接到改造數據同步表單的需求。 一方面,[數據同步模塊](https://www.dtstack.com/dtinsight?src=szsm)的代碼可讀性和可維護性較差,導致在數據 ...
  • 閑來無事,看到“短文學網”文章內容還算整潔,而且非常容易進行採集,於是也就手癢了弄了一下,速度非常快可能與網路沒有大量廣告啊、JS啊有關。 詳細的分類信息如下: qq日誌包含有:qq空間(2098)條、非主流日(180)條、搞笑日誌(132)條、個性日誌(204)條、經典日誌(260)條、空間文字( ...
  • 常識類的知識比較被大家接受,博客也好,微博也好,似乎常識類的都不會讓你成為冷門,主要看是的經營發展、營銷推廣的方式好壞。而一些流行的常識類一般只是少量的一兩句話,今天採集了“生活小知識”(http://www.xiaozhishi.com)網站的1萬四千多條內容: 內容沒有去除“ [來源:生活小知識 ...
  • 《1萬多中草藥中藥材大全ACCESS資料庫》採集的是全國中草藥的信息,內容包含別名,來源,性味,主治,使用方法和用量,外形,化學成分等眾多實用信息。 至於為什麼數據內容中包含HTML代碼,因為這些HTML代碼是調整顯示格式作用的,沒有了這些HTML代碼,內容排版方面就不層次分明瞭,效果其實可以看這裡 ...
  • # GreatSQL刪除分區慢的跟蹤 ## 背景 某業務系統,每天凌晨會刪除分區表的一個分區(按天分區),耗時較久,從最開始的30秒,慢慢變為1分鐘+,影響到交易業務的正常進行。 在測試環境進行了模擬,復現了刪除分區慢的情況,本次基於GreatSQL8.0.25-17進行測試,官方mysql版本也存 ...
  • # zookeeper ZooKeeper是一個開源的分散式應用程式協調服務 簡單來說可以理解為zookeeper = 文件系統+監聽通知機制 應用場景: 1. 集群管理、伺服器狀態感知 2. 分散式應用配置管理 3. 統一命名服務 4. 分散式鎖 > 小總結: >1. 為客戶提供寫數據功能 數據不 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...