1. 引言
前面已經(jīng)知道了協(xié)程作用域和協(xié)程取消的真正作用了勒奇,現(xiàn)在結合著協(xié)程作用域和withContext
來再次體會下協(xié)程取消的便捷预鬓。
2. 實踐代碼說明
本文關鍵代碼(按鈕的點擊事件):
viewBinding.launchBtn -> {
"Clicked launchBtn".let {
myLog(it)
}
scope.launch(Dispatchers.IO) {
"Coroutine IO runs (from launchBtn)".let {
myLog(it)
}
Thread.sleep(FIVE_SECONDS)
"Coroutine IO runs after thread sleep".let {
myLog(it)
}
withContext(Dispatchers.Main) {
"withContext(Dispatchers.Main) lambda".let {
myLog(it)
}
}
}
}
關鍵的代碼邏輯很簡單——
啟動一個在IO線程的協(xié)程壹将,協(xié)程輸出第一行l(wèi)og——"Coroutine IO runs (from launchBtn)"奢方;
然后休眠線程5秒,輸出第二行l(wèi)og——"Coroutine IO runs after thread sleep"齿诉;
最后切換到主線程竣蹦,輸出第三行l(wèi)og——"withContext(Dispatchers.Main) lambda"顶猜。
這里所用的協(xié)程作用域跟前篇一樣,是Activity中的屬性:
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
生命周期onDestroy
中:
override fun onDestroy() {
super.onDestroy()
scope.cancel()
}
另外一個按鈕痘括,點擊時取消已經(jīng)啟動的協(xié)程:
viewBinding.cancelBtn -> {
"Clicked cancelBtn".let {
myLog(it)
}
scope.coroutineContext.cancelChildren()
}
3. 實踐過程說明
在啟動協(xié)程后长窄,5秒以內(nèi)點擊取消按鈕或者退出當前頁面,可以發(fā)現(xiàn)纲菌,協(xié)程的前兩行l(wèi)og會始終輸出挠日,但是在第三行l(wèi)og卻不會輸出,不點擊取消按鈕和始終停留在當前頁面的話翰舌,第三行l(wèi)og會正常輸出嚣潜。
為什么取消后第二行l(wèi)og始終輸出,而第三行l(wèi)og不輸出了呢椅贱?懂算?
在一學就會的協(xié)程使用——基礎篇(三)初遇協(xié)程取消中,提及過協(xié)程的取消是需要協(xié)作的庇麦,也就是說计技,協(xié)程的取消需要在執(zhí)行邏輯中需要有協(xié)作點!初遇篇所用的協(xié)程取消點主要是isActive
和ensureActive()
女器,這里初看并沒有協(xié)程取消協(xié)作點酸役,為什么第三行l(wèi)og不支持了呢?
這里便是本文的重點,所有kotlinx.coroutines包下的掛起函數(shù)都是可被取消的:
所有
kotlinx.coroutines
中的掛起函數(shù)都是 可被取消的 涣澡。它們檢查協(xié)程的取消贱呐, 并在取消時拋出 CancellationException。
上述描述出自中文文檔:https://www.kotlincn.net/docs/reference/coroutines/cancellation-and-timeouts.html
withContext
是kotlinx.coroutines包下的掛起函數(shù)入桂,如上描述奄薇,是可被取消的。所以協(xié)程在執(zhí)行到withContext
一行時抗愁,觸發(fā)到協(xié)作點時馁蒂,如果協(xié)程已經(jīng)被取消,所以協(xié)作點生效蜘腌。
這里便是解釋了第三行l(wèi)og在取消后不再輸出的問題沫屡。進一步地,為什么第二行l(wèi)og的執(zhí)行時機也在協(xié)程取消以后撮珠,但第二行l(wèi)og始終會輸出呢沮脖?這里必須再強調(diào):
協(xié)程的取消是 協(xié)作 的。一段協(xié)程代碼必須協(xié)作才能被取消芯急。
也就是說勺届,協(xié)程代碼執(zhí)行過程中,如果沒有取消協(xié)作點娶耍,即使在協(xié)程執(zhí)行到具體代碼位置時協(xié)程已經(jīng)被取消免姿,協(xié)程仍會繼續(xù)執(zhí)行!
在第二行代碼執(zhí)行之時榕酒,沒有任何協(xié)程取消協(xié)作點胚膊,所以不管執(zhí)行第二行l(wèi)og輸出之時協(xié)程是否已經(jīng)被取消,第二行l(wèi)og始終會輸出奈应。
第三行l(wèi)og不輸出澜掩,是因為掛起函數(shù)withContext
是可取消的,也就是在withContext
掛起函數(shù)執(zhí)行的時候杖挣,才觸發(fā)了協(xié)程取消的協(xié)作點肩榕,進而使得協(xié)程取消!
切記惩妇,協(xié)程取消不是萬能鑰匙株汉,調(diào)用了協(xié)程的取消后,協(xié)程并不能在任意位置停止執(zhí)行歌殃,只有執(zhí)行到協(xié)作點的時候乔妈,協(xié)程的取消才會生效!
其實氓皱,想要在5秒內(nèi)點擊取消后第二行l(wèi)og也不輸出路召,也很簡單勃刨,在第二行l(wèi)og輸出前,增加協(xié)程取消的協(xié)作點股淡,即調(diào)用ensureActive()
函數(shù)即可身隐!
4. 關于掛起函數(shù)的提醒
文檔中描述,kotlinx.coroutines
中的掛起函數(shù)都是可被取消的唯灵。注意限定詞贾铝,可被取消的不是掛起函數(shù),是kotlinx.coroutines
中的掛起函數(shù)埠帕。
也就是說垢揩,掛起函數(shù)本身是不提供取消功能,只不過是kotlinx.coroutines
中的掛起函數(shù)中實現(xiàn)了對協(xié)程取消的協(xié)作代碼敛瓷。這里主要強調(diào)第一個容易誤解的點:
掛起函數(shù)本身不會提供協(xié)程取消協(xié)作點叁巨,而是協(xié)程特定包下中的掛起函數(shù)內(nèi)部實現(xiàn)代碼提供了取消協(xié)作點。
事實上琐驴,上面說的可取消的掛起函數(shù)還限定了在kotlinx.coroutines
中俘种,這句話簡直就是完美且準確!
注意啊绝淡,這個包名的第一個是kotlinx,后面是有x的苍姜,Kotlin的包名中有一個跟這個很相似的牢酵,是kotlin.coroutines
,人家開發(fā)文檔可沒說kotlin.coroutines下面的掛起函數(shù)是可取消的衙猪!
比如suspendCoroutine
就是不帶x的包名下的函數(shù)馍乙,所以這個掛起函數(shù)并不可取消。相對地垫释,實現(xiàn)相同功能又可取消的函數(shù)為suspendCancellableCoroutine
丝格,這個函數(shù)所在的包名是帶x的。
這里suspendCoroutine
和suspendCancellableCoroutine
兩個函數(shù)都是掛起函數(shù)棵譬,一個不可取消显蝌,一個可取消,另一方面也可以說明掛起函數(shù)并不總是支持取消協(xié)作的订咸,取消的協(xié)作本質(zhì)在掛起函數(shù)內(nèi)部執(zhí)行邏輯而與掛起函數(shù)無關曼尊。
這里,不妨再結合本文和一學就會的協(xié)程使用——基礎篇(三)初遇協(xié)程取消中的代碼脏嚷,思考一下骆撇,協(xié)程的取消需要怎樣的配合才能發(fā)揮取消的實際作用?
5. 樣例工程代碼
代碼樣例Demo父叙,見Github:https://github.com/TeaCChen/CoroutineStudy
本文示例代碼神郊,如覺奇怪或啰嗦肴裙,其實為CancelStepTwoActivity.kt
中的代碼摘取主要部分說明,在demo代碼當中涌乳,為提升細節(jié)內(nèi)容蜻懦,有更加多的封裝和輸出內(nèi)容。
本文的頁面截圖示例如下:
一學就會的協(xié)程使用——基礎篇(一)協(xié)程啟動
一學就會的協(xié)程使用——基礎篇(三)初遇協(xié)程取消
一學就會的協(xié)程使用——基礎篇(四)協(xié)程作用域
一學就會的協(xié)程使用——基礎篇(五)再遇協(xié)程取消(本文)