vs 啟動調用的目標發生異常_協程中的取消和異常 | 取消操作詳解

d77f05a29e737b8125fd9865602cd9e8.png在日常的開發中,我們都知道應該避免不必要的任務處理來節省設備的內存空間和電量的使用——這一原則在協程中同樣適用。您需要控制好協程的生命周期,在不需要使用的時候將它取消,這也是結構化并發所倡導的,繼續閱讀本文來了解有關協程取消的來龍去脈。?? 為了能夠更好地理解本文所講的內容,建議您首先閱讀本系列中的第一篇文章:?協程中的取消和異常 | 核心概念介紹。

調用 cancel 方法

當啟動多個協程時,無論是追蹤協程狀態,還是單獨取消各個協程,都是件讓人頭疼的事情。不過,我們可以通過直接取消協程啟動所涉及的整個作用域 (scope) 來解決這個問題,因為這樣可以取消所有已創建的子協程。
// 假設我們已經定義了一個作用域val job1 = scope.launch { … }val job2 = scope.launch { … }scope.cancel()

取消作用域會取消它的子協程

有時候,您也許僅僅需要取消其中某一個協程,比如用戶輸入了某個事件,作為回應要取消某個進行中的任務。如下代碼所示,調用 job1.cancel 會確保只會取消跟 job1 相關的特定協程,而不會影響其余兄弟協程繼續工作。
// 假設我們已經定義了一個作用域val job1 = scope.launch { … }val job2 = scope.launch { … }?// 第一個協程將會被取消,而另一個則不受任何影響job1.cancel()

被取消的子協程并不會影響其余兄弟協程

協程通過拋出一個特殊的異常 CancellationException 來處理取消操作。在調用 .cancel 時您可以傳入一個 CancellationException 實例來提供更多關于本次取消的詳細信息,該方法的簽名如下:
fun cancel(cause: CancellationException? = null)
如果您不構建新的 CancellationException 實例將其作為參數傳入的話,會創建一個默認的 CancellationException (請查看完整代碼)。
public override fun cancel(cause: CancellationException?) {    cancelInternal(cause ?: defaultCancellationException())}
  • 完整代碼https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/common/src/JobSupport.kt#L612
一旦拋出了 CancellationException 異常,您便可以使用這一機制來處理協程的取消。有關如何執行此操作的更多信息,請參考下面的處理取消的副作用一節。在底層實現中,子協程會通過拋出異常的方式將取消的情況通知到它的父級。父協程通過傳入的取消原因來決定是否來處理該異常。如果子協程因為 CancellationException 而被取消,對于它的父級來說是不需要進行其余額外操作的。不能在已取消的作用域中再次啟動新的協程如果您使用的是 androidx KTX 庫的話,在大部分情況下都不需要創建自己的作用域,所以也就不需要負責取消它們。如果您是在 ViewModel 的作用域中進行操作,請使用 viewModelScope,或者如果在生命周期相關的作用域中啟動協程,那就應該使用 lifecycleScope。viewModelScope 和 lifecycleScope 都是 CoroutineScope 對象,它們都會在適當的時間點被取消。例如,當 ViewModel 被清除時,在其作用域內啟動的協程也會被一起取消。
  • viewModelScope

    https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#(androidx.lifecycle.ViewModel).viewModelScope:kotlinx.coroutines.CoroutineScope

  • lifecycleScope

    https://developer.android.google.cn/reference/kotlin/androidx/lifecycle/package-summary#lifecyclescope

為什么協程處理的任務沒有停止?

如果我們僅是調用了 cancel 方法,并不意味著協程所處理的任務也會停止。如果您使用協程處理了一些相對較為繁重的工作,比如讀取多個文件,那么您的代碼不會自動就停止此任務的進行。

讓我們舉一個更簡單的例子看看會發生什么。假設我們需要使用協程來每秒打印兩次?"Hello"。我們先讓協程運行一秒,然后將其取消。其中一個版本實現如下所示:

30263f9337e97f3d4032277911a2d33c.gif

我們一步一步來看發生了什么。當調用 launch 方法時,我們創建了一個活躍 (active) 狀態的協程。緊接著我們讓協程運行了 1,000 毫秒,打印出來的結果如下:
Hello 0Hello 1Hello?2
當 job.cancel 方法被調用后,我們的協程轉變為取消中 (cancelling) 的狀態。但是緊接著我們發現 Hello 3 和 Hello 4 打印到了命令行中。當協程處理的任務結束后,協程又轉變為了已取消 (cancelled) 狀態。協程所處理的任務不會僅僅在調用 cancel 方法時就停止,相反,我們需要修改代碼來定期檢查協程是否處于活躍狀態。

讓您的協程可以被取消

您需要確保所有使用協程處理任務的代碼實現都是協作式的,也就是說它們都配合協程取消做了處理,因此您可以在任務處理期間定期檢查協程是否已被取消,或者在處理耗時任務之前就檢查當前協程是否已取消。例如,如果您從磁盤中獲取了多個文件,在開始讀取文件內容之前,先檢查協程是否被取消了。類似這樣的處理方式,您可以避免處理不必要的 CPU 密集型任務。

val job = launch {????for(file?in?files)?{        // TODO 檢查協程是否被取消        readFile(file)    }}
所有 kotlinx.coroutines 中的掛起函數 (withContext, delay 等) 都是可取消的。如果您使用它們中的任一個函數,都不需要檢查協程是否已取消,然后停止任務執行,或是拋出 CancellationException 異常。但是,如果沒有使用這些函數,為了讓您的代碼能夠配合協程取消,可以使用以下兩種方法:
  • 檢查 job.isActive 或者使用 ensureActive()
  • 使用 yield() 來讓其他任務進行

檢查 job 的活躍狀態

先看一下第一種方法,在我們的 while(i<5) 循環中添加對于協程狀態的檢查:
// 因為處于 launch 的代碼塊中,可以訪問到 job.isActive 屬性while (i < 5 && isActive)
這樣意味著我們的任務只會在協程處于活躍的狀態下執行。同樣,這也意味著在 while 循環之外,我們若還想處理別的行為,比如在 job 被取消后打日志出來,那就可以檢查 !isActive 然后再繼續進行相應的處理。

Coroutine 的代碼庫中還提供了另一個很有用的方法 —— ensureActive(),它的實現如下:

fun Job.ensureActive(): Unit {    if (!isActive) {         throw getCancellationException()    }}
?

如果 job 處于非活躍狀態,這個方法會立即拋出異常,我們可以在 while 循環開始就使用這個方法。

while (i < 5) {    ensureActive()    …}

通過使用 ensureActive 方法,您可以避免使用 if 語句來檢查 isActive 狀態,這樣可以減少樣板代碼的使用量,但是相應地也失去了處理類似于日志打印這種行為的靈活性。

使用 yield() 函數運行其他任務

如果要處理的任務屬于 1) CPU 密集型,2) 可能會耗盡線程池資源,3) 需要在不向線程池中添加更多線程的前提下允許線程處理其他任務,那么請使用 yield()。如果 job 已經完成,由 yield 所處理的首要任務將會是檢查任務的完成狀態,完成的話則直接通過拋出 CancellationException 來退出協程。yield 可以作為定期檢查所調用的第一個函數,例如上面提到的 ensureActive() 方法。
  • yield()

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html

Job.join ??Deferred.await cancellation

等待協程處理結果有兩種方法:?來自 launch 的 job 可以調用 join 方法,由 async 返回的 Deferred (其中一種 job 類型) 可以調用 await 方法。Job.join 會掛起協程,直到任務處理完成。與 job.cancel 一起使用時,會按照以下方式進行:
  • 如果您調用? job.cancel 之后再調用 job.join,那么協程會在任務處理完成之前一直處于掛起狀態;
  • 在 job.join 之后調用 job.cancel 沒有什么影響,因為 job 已經完成了。

  • Job.join

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html

如果您關心協程處理結果,那么應該使用 Deferred。當協程完成后,結果會由 Deferred.await 返回。Deferred 是 Job 的其中一種類型,它同樣可以被取消。
  • Deferred

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html

  • Deferred.await

    https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html

在已取消的 deferred 上調用 await 會拋出 JobCancellationException 異常。

val deferred = async { … }deferred.cancel()val?result?=?deferred.await()?//?拋出?JobCancellationException?異常

為什么會拿到這個異常呢?await 的角色是負責在協程處理結果出來之前一直將協程掛起,因為如果協程被取消了那么協程就不會繼續進行計算,也就不會有結果產生。因此,在協程取消后調用 await 會拋出 JobCancellationException 異常: 因為 Job 已被取消。

另一方面,如果您在 deferred.cancel 之后調用 deferred.await 不會有任何情況發生,因為協程已經處理結束。

處理協程取消的副作用

假設您要在協程取消后執行某個特定的操作,比如關閉可能正在使用的資源,或者是針對取消需要進行日志打印,又或者是執行其余的一些清理代碼。我們有好幾種方法可以做到這一點:

檢查 !isActive

如果您定期地進行 isActive 的檢查,那么一旦您跳出 while 循環,就可以進行資源的清理。之前的代碼可以更新至如下版本:

while?(i?5?&&?isActive)?{    if (…) {        println(“Hello ${i++}”)        nextPrintTime += 500L    }}?// 協程所處理的任務已經完成,因此我們可以做一些清理工作println(“Clean?up!”)
您可以查看完整版本。
  • 完整版本

    https://pl.kotl.in/loI9DaIYj

所以現在,當協程不再處于活躍狀態,會退出 while 循環,就可以處理一些清理工作了。

Try catch finally

因為當協程被取消后會拋出 CancellationException 異常,我們可以將掛起的任務放置于 try/catch 代碼塊中,然后在 finally 代碼塊中執行需要做的清理任務。
val job = launch {   try {      work()   } catch (e: CancellationException){      println(“Work cancelled!”)    } finally {      println(“Clean up!”)    }}delay(1000L)println(“Cancel!”)job.cancel()println(“Done!”)
但是,一旦我們需要執行的清理工作也掛起了,那上述代碼就不能夠繼續工作了,因為一旦協程處于取消中狀態,它將不能再轉為掛起 (suspend) 狀態。您可以查看完整代碼。
  • 完整代碼https://pl.kotl.in/wjPINnWfG
處于取消中狀態的協程不能夠掛起

當協程被取消后需要調用掛起函數,我們需要將清理任務的代碼放置于 NonCancellable CoroutineContext 中。這樣會掛起運行中的代碼,并保持協程的取消中狀態直到任務處理完成。

val job = launch {   try {      work()   } catch (e: CancellationException){      println(“Work cancelled!”)    } finally {      withContext(NonCancellable){         delay(1000L) // 或一些其他的掛起函數         println(“Cleanup done!”)      }    }}delay(1000L)println(“Cancel!”)job.cancel()println(“Done!”)
您可以查看其工作原理。
  • 工作原理https://pl.kotl.in/ufZRQSa7o

suspendCancellableCoroutine 和 invokeOnCancellation

如果您通過 suspendCoroutine 方法將回調轉為協程,那么您更應該使用 suspendCancellableCoroutine 方法。可以使用 continuation.invokeOnCancellation 來執行取消操作:

suspend fun work() {   return suspendCancellableCoroutine { continuation ->???????continuation.invokeOnCancellation?{?          // 處理清理工作???????}   // 剩余的實現代碼}

為了享受到結構化并發帶來的好處,并確保我們并沒有進行多余的操作,那么需要保證代碼是可被取消的。

使用在 Jetpack: viewModelScope 或者 lifecycleScope 中定義的 CoroutineScopes,它們在 scope 完成后就會取消它們處理的任務。如果要創建自己的 CoroutineScope,請確保將其與 job 綁定并在需要時調用 cancel。

協程代碼的取消需要是協作式的,因此請將代碼更新為對協程的取消操作以延后的方式進行檢查,并避免不必要的操作。

現在,大家了解了本系列的第一部分協程的一些基本概念、第二部分協程的取消,在接下來的文章中,我們將繼續深入探討學習第三部分異常處理,感興趣的讀者請繼續關注我們的更新。

6f8770c36ad372673a5bb3ca9c8a2cc5.png


推薦閱讀

0e09c956922106dfcbd0e6ee3e67cdd5.png8ccbf5a41a9e2abcb1a5410385104782.pngb83135783517d761310de3687858ba55.png3e96c6e5f134bee3f592a067566989d2.gif點擊屏末?|??|?查看 Android 官方中文文檔 —— 使用 Kotlin 更快地編寫更出色的 Android 應用

b964e1f09acdf0fc035e5383f3e9cc76.png

ccbd0e1a9fd4e8e14010392962d47729.gif

1f5cdcc544a7dfbabd3f9f1841b3b20b.png

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/539100.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/539100.shtml
英文地址,請注明出處:http://en.pswp.cn/news/539100.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java http 下載文件_JAVA通過HttpURLConnection 上傳和下載文件的方法

本文介紹了JAVA通過HttpURLConnection 上傳和下載文件的方法&#xff0c;分享給大家&#xff0c;具體如下&#xff1a;HttpURLConnection文件上傳HttpURLConnection采用模擬瀏覽器上傳的數據格式&#xff0c;上傳給服務器上傳代碼如下&#xff1a;package com.util;import java…

mkdir-yum-tree命令應用案例

案例&#xff1a; 請用一條命令完成目錄創建/hello/world/test 解答&#xff1a; mkdir -p /hello/world/test -p 遞歸創建目錄&#xff0c;沒有子目錄創建。 案例&#xff1a; 打印hello/目錄的結構 [roothello110 ~]# tree hello/ -bash: tree: command not found 發…

pytorch 圖像分割的交并比_Segmentation101系列-最簡單的卷積網絡語義分割(1)-PASCAL VOC圖像分割...

作者&#xff1a;陳洪瀚 /洪瀚筆記知乎專欄摘要&#xff1a;介紹了使用PyTorch和torchvision加載訓練好的全卷積網絡FCN或DeepLab模型&#xff0c;并對PASCAL VOC圖像進行分割并顯示結果。網址&#xff1a;github代碼鏈接, 碼云代碼鏈接陳洪瀚?www.zhihu.com一. 準備實驗數據下…

python selenium chrome獲取每個請求內容_python+selenium調用chrome打開網址獲取內容

通過selenium庫&#xff0c;python可以調用chrome打開指定網頁并獲取網頁內容或者模擬登陸獲取網頁內容1&#xff0c;安裝selenium和配置chromedriver安裝seleniumC:\Users\cord> pip install selenium配置chromedriver該下載什么版本根據瀏覽器版本以及附錄的版本對照表下載…

系統目錄結構 ls命令 文件類型 alias命令

2019獨角獸企業重金招聘Python工程師標準>>> 2.1/2.2 系統目錄結構 /bin&#xff1a;bin是Binary的縮寫&#xff0c;該目錄下存放的是最常用的命令。 /boot&#xff1a;該目錄下存放的是啟動Linux時使用的一些核心文件&#xff0c;包括一些連接文件以及鏡像文件。 …

運維老鳥教你安裝centos6.5如何選擇安裝包

原文&#xff1a;http://oldboy.blog.51cto.com/2561410/1564620 ------------------------------------------------------------------------------ 近來發現越來越多的運維小伙伴們都有最小化安裝系統的潔癖,因此&#xff0c;找老男孩來咨詢&#xff0c;這個“潔癖”好習慣…

服務器centos怎么部署_我什么都不會,怎么擁有自己的個人博客呢

博客每個人都想擁有一個屬于自己的博客&#xff0c;可以分享自己的心得、技術等&#xff0c;可以很好地展示自己的作品&#xff0c;但是自己又什么都不會怎么才能擁有自己的個人博客呢&#xff1f;一、搭建個人博客需要什么呢(1)購買服務器&#xff0c;個人博客可以購買香港服務…

java 過濾器 過濾文件中的文件_Java 使用FileFilter過濾器對文件進行搜索

FileFilter概述java.io.FileFilter是一個接口&#xff0c;是File的過濾器。該接口的對象可以傳遞給File類的listFiles(FileFilter filter)作為參數&#xff0c;FileFilter接口中只有一個方法。boolean accept(File pathname):測試pathname是否應該包含在當前File目錄中&#xf…

修改yum的鏡像服務器為阿里云

1、進入阿里云鏡像網站 http://mirrors.aliyun.com/ 2、選擇centos---help 3、安裝help里的步驟進行操作 1、備份 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.backup 2、下載新的CentOS-Base.repo 到/etc/yum.repos.d/ CentOS 5 wget -O /e…

面試記錄

東信北郵 智能終端開發工程師 筆試部分 首先去做了一套筆試題&#xff0c;前面選擇題都是android基礎&#xff0c;后面是sql語句。 有一個問題說的是runtime exception&#xff0c;有四個選項&#xff1a; a. ArithmeticException b. lllegalArgumentException c. NullPointerE…

python有類似mybatis的框架_為什么感覺國內比較流行的 mybatis 在國外好像沒人用的樣子?...

892019-03-30 21:23:21 08:00 1看了這么多回復。忍不住了&#xff01;1. hibernate 歷史悠久并不代表過時&#xff0c;mybatis 這種方式就是未來嗎&#xff1f;肯定不是。數據庫就是用來存數據的&#xff0c;聯表查詢一大堆只能說明數據結構設計是有問題的&#xff0c;只是不…

c# 模擬登陸 webbrowser 抓取_《VR+電力——更換絕緣子培訓》已登陸Pico Neo 2

原標題&#xff1a;《VR電力——更換絕緣子培訓》已登陸Pico Neo 2

java instanceof 繼承_Java中的instanceof關鍵字

Java中&#xff0c;instanceof運算符的前一個操作符是一個引用變量&#xff0c;后一個操作數通常是一個類(可以是接口)&#xff0c;用于判斷前面的對象是否是后面的類&#xff0c;或者其子類、實現類的實例。如果是返回true&#xff0c;否則返回false。也就是說&#xff1a;使用…

中文導致Mybatis無效的列索引

<!-- 普鐵 --><select id"selectTrainSceneThrough" parameterType"HashMap" resultType"HashMap">select ROUND(("普鐵用戶專網總流量KB""普鐵用戶公網總流量KB")/1024/1024,3) as total_dataflow,"普鐵用…

python怎么創建配置文件_如何寫python的配置文件

一、創建配置文件在D盤建立一個配置文件&#xff0c;名字為&#xff1a;test.ini內容如下&#xff1a;[baseconf]host127.0.0.1port3306userrootpasswordrootdb_namegloryroad[test]ip127.0.0.1int1float1.5boolTrue注意&#xff1a;要將文件保存為ansi編碼&#xff0c;utf-8編…

學習筆記-JMeter 進行接口壓力測試

一、壓力測試場景設置 1、場景設定&#xff1a;進行接口壓力測試時&#xff0c;有單場景也有混合場景。單場景就是對一個接口進行請求&#xff1b;混合場景需要對多個接口進行請求&#xff0c;在流程類業務場景會運用到 2、壓測時間設定&#xff1a;通常時間為10&#xff0d;15…

Linux的 .bashrc 和.bash_profile和.profile文件

linux啟動或是每次打開一個shell的時候都會執行用戶家目錄下的.bashrc文件&#xff0c;所有可以在這個文件里面添加一些內容&#xff0c;以便Linux每次啟動時都會執行相應的內容。 如果ssh方式遠程登錄Linux時&#xff0c;會自動執行用戶家目錄下的.bash_profile文件&#xff0…

四宮格效果 css_【深度教研】智力游戲“九宮格” 集體教研活動紀實

【關鍵詞】教研要建立過程模式&#xff0c;規范管理&#xff0c;分層推進&#xff0c;各負其責&#xff0c;及時反饋&#xff0c;展示總結。讓教研的過程成為全體教師共同成長的過程。游戲和材料不是一次性的制作和一次性的使用&#xff0c;其價值在于反復玩&#xff0c;玩中學…

java oracle 排序_Oracle的排序和限制條件(order by 和where)

1、Order by子句的使用select column....from ....order by ...1) Order by子句在整個select語句中的位置&#xff1a;始終位于最后2) order by后可以跟什么&#xff1a;列名&#xff0c;列的別名&#xff0c;表達式&#xff0c;列出現在select關鍵字后的順序(列號);3) order b…

kettle使用_ETL工具(kettle)-《PentahoKettle解決方案-使用PDI構建開源ETL解決方案》

&#xfeff;Matt Casters的博客:http://www.ibridge.be/、 www.kettle.be書籍:《Pentaho Kettle解決方案&#xff1a;使用PDI構建開源ETL解決方案》 鏈接&#xff1a;https://pan.baidu.com/s/15iUOWOCb8g_YLo5WN9fh0A 提取碼&#xff1a;5upfkettle起源Kettle一詞起源于“KDE…