一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(三)初遇取消

1. 引言

協(xié)程支持取消挡闰,也就是說,啟動(dòng)一個(gè)協(xié)程后而且在協(xié)程結(jié)束前已經(jīng)不希望協(xié)程再執(zhí)行代碼了掰盘,可以對(duì)協(xié)程進(jìn)行取消摄悯。

如果只知道協(xié)程的取消,而并不知道協(xié)程的取消需要代碼配合愧捕,將會(huì)導(dǎo)致不符合預(yù)期的執(zhí)行結(jié)果奢驯。

2. 協(xié)程的取消

其實(shí),在每次通過launch啟動(dòng)協(xié)程次绘,該函數(shù)都會(huì)返回一個(gè)Job對(duì)象瘪阁。

val job = GlobalScope.launch(Dispatchers.IO) {
    // ...
}

這個(gè)Job對(duì)象理解為協(xié)程的句柄撒遣,可以用來管理協(xié)程的生命周期。

協(xié)程的生命周期設(shè)計(jì)的內(nèi)容相當(dāng)豐富管跺,這里作為基礎(chǔ)篇部分不會(huì)鋪開所有的知識(shí)點(diǎn)(事實(shí)上义黎,如果鋪開這方面的知識(shí)點(diǎn),將會(huì)牽扯到協(xié)程非常多的設(shè)計(jì)概念和理解內(nèi)容豁跑,一下子吃太多廉涕,容易消化不良)。

本文主要介紹的內(nèi)容便是協(xié)程生命周期里的取消部分:在不再需要協(xié)程的時(shí)候艇拍,提前讓協(xié)程取消狐蜕。

取消的主要API,即使前面提及的Job對(duì)象的取消函數(shù):

job.cancel()

當(dāng)在某個(gè)頁面(比如Activity)中啟動(dòng)了一個(gè)協(xié)程卸夕,然后在頁面退出后层释,如協(xié)程仍未執(zhí)行完成,這時(shí)候最好將協(xié)程取消快集。

所以也很簡(jiǎn)單贡羔,在頁面內(nèi)所有啟動(dòng)協(xié)程的地方,將啟動(dòng)協(xié)程的Job對(duì)象保存个初,然后在頁面銷毀的回調(diào)中調(diào)用取消即可乖寒。

事實(shí)上,這樣是不夠的勃黍。取消不是萬能的,并不能在協(xié)程任意執(zhí)行點(diǎn)結(jié)束線程晕讲,而是需要協(xié)程執(zhí)行代碼中的API調(diào)用配合覆获。

3. 協(xié)程沒有達(dá)到取消效果的樣例

為了說明,這里寫出一段代碼瓢省,主要邏輯是啟動(dòng)協(xié)程弄息,協(xié)程內(nèi)的執(zhí)行主要邏輯為:5秒內(nèi)不斷循環(huán)打印log的操作,10秒后協(xié)程執(zhí)行完成勤婚。

如下:

val job = GlobalScope.launch(Dispatchers.IO) {摹量、
    "Coroutine IO runs (from loopBtn)".let {
        myLog(it)
    }
    var curMillis = System.currentTimeMillis()
    val targetMilli = curMillis + FIVE_SECONDS
    while (curMillis < targetMilli) {
        val msg = "looping (from loopBtn)"
        myLog(msg)
        curMillis = System.currentTimeMillis()
    }
    "loop finished (from loopBtn)".let {
        myLog(it)
    }
}

然后通過一個(gè)List將job收集起來:

jobList.add(job)

然后在取消按鈕監(jiān)聽中將jobList中每個(gè)Job對(duì)象調(diào)用取消:

jobList.forEach {
    it.cancel()
}

那么,請(qǐng)問馒胆,如果在啟動(dòng)協(xié)程后的5秒之內(nèi)缨称,點(diǎn)擊取消按鈕觸發(fā)協(xié)程的取消方法,協(xié)程最后一行的"loop finished (from loopBtn)"這一行l(wèi)og仍會(huì)不會(huì)輸出祝迂?

最終結(jié)果上睦尽,是會(huì)輸出的,即使在點(diǎn)擊了取消按鈕以后型雳,循環(huán)體中的log也仍然在不斷輸出当凡!

這便是山害,協(xié)程的取消并不是萬能,并不是說啟動(dòng)了協(xié)程后在協(xié)程結(jié)束前調(diào)用了取消函數(shù)沿量,協(xié)程自然就會(huì)中斷而停下浪慌!也就是說,在這段代碼當(dāng)中朴则,協(xié)程完全不會(huì)按照“理所當(dāng)然”地產(chǎn)生想要的取消效果权纤!

事實(shí)上,協(xié)程的取消是協(xié)作的佛掖,?段協(xié)程代碼必須協(xié)作才能被取消妖碉!

上面的協(xié)程代碼,即缺少了協(xié)作部分芥被!

4. 協(xié)程取消協(xié)作途徑——變量判斷

在協(xié)程取消協(xié)作當(dāng)中欧宜,一個(gè)不得不提的變量是isActive

通過這個(gè)變量,便可以知道協(xié)程在執(zhí)行的過程中是否已經(jīng)被取消拴魄。

事實(shí)上冗茸,這個(gè)變量在協(xié)程調(diào)用取消后,必然為false匹中,但是反過來并不會(huì)成立夏漱,也就是這個(gè)變量是false,并不能說明協(xié)程被取消顶捷。

所以挂绰,在第3節(jié)的代碼當(dāng)中,補(bǔ)充這個(gè)條件的判斷:

val job = GlobalScope.launch(Dispatchers.IO) {服赎、
    "Coroutine IO runs (from loopBreakBtn)".let {
        myLog(it)
    }
    var curMillis = System.currentTimeMillis()
    val targetMilli = curMillis + FIVE_SECONDS
    while (curMillis < targetMilli) {
        if (!isActive) {
            break
        }
        val msg = "looping (from loopBreakBtn)"
        myLog(msg)
        curMillis = System.currentTimeMillis()
    }
    "loop finished (from loopBreakBtn)".let {
        myLog(it)
    }
}

這樣就會(huì)發(fā)現(xiàn)葵蒂,當(dāng)這個(gè)job被取消以后,循環(huán)代碼中的log不再輸出重虑,也就是循環(huán)已經(jīng)跳出践付,但協(xié)程最后的一行l(wèi)og仍會(huì)輸出。

如果這個(gè)是理想效果缺厉,那么可以到底為止永高。

5. 協(xié)程取消協(xié)作途徑——函數(shù)檢測(cè)

事實(shí)上,更多的情況下的需求應(yīng)該是提针,在協(xié)程取消后命爬,不再執(zhí)行后續(xù)任何代碼,也就是說辐脖,僅僅跳出循環(huán)是不夠的遇骑,我們希望循環(huán)后面的所有代碼都不再執(zhí)行!

這種情況上揖曾,在協(xié)程取消協(xié)作上落萎,有更好的選擇亥啦,函數(shù)檢測(cè),具體的函數(shù)為:

ensureActive()

所以练链,進(jìn)一步得翔脱,將協(xié)程執(zhí)行代碼再次修改為如下執(zhí)行邏輯:

val job = GlobalScope.launch(Dispatchers.IO) {
    "Coroutine IO runs (from loopEnsureBtn)".let {
        myLog(it)
    }
    var curMillis = System.currentTimeMillis()
    val targetMilli = curMillis + FIVE_SECONDS
    while (curMillis < targetMilli) {
        ensureActive()
        val msg = "looping (from loopEnsureBtn)"
        myLog(msg)
        curMillis = System.currentTimeMillis()
    }
    "loop finished (from loopEnsureBtn)".let {
        myLog(it)
        stringBuilder.append(buildUIMsg(it))
    }
}

這樣的話,在協(xié)程取消以后媒鼓,無論是循環(huán)體還是循環(huán)體后面的log届吁,都不會(huì)再輸出,達(dá)到了理想的協(xié)程取消效果绿鸣!

6. 樣例工程代碼

代碼樣例Demo疚沐,見Github:https://github.com/TeaCChen/CoroutineStudy

本文示例代碼,如覺奇怪或啰嗦潮模,其實(shí)為CancelStepOneActivity.kt中的代碼摘取主要部分說明亮蛔,在demo代碼當(dāng)中,為提升細(xì)節(jié)內(nèi)容擎厢,有更加多的封裝和輸出內(nèi)容究流。

本文不再列舉所有的log輸出內(nèi)容和情況進(jìn)行比較,而是直接給出結(jié)論动遭,具體的log輸出檢驗(yàn)可自行檢驗(yàn)芬探。

本文的頁面截圖如下:

image-3-1.png

7. 引申問題

本文所有的isActiveensureActive()均是協(xié)程作用域的拓展屬性或方法,其實(shí)本質(zhì)也是協(xié)程上下文的同名變量或方法的進(jìn)一步封裝厘惦,而協(xié)程上下文偷仿,其實(shí)是協(xié)程所有的地方都可以拿到的變量,而協(xié)程作用域僅在個(gè)別協(xié)程API上可用宵蕉。

畫外音:忍自途病!不要展開国裳!這里是基礎(chǔ)篇不作鋪展形入!

最后全跨,必須強(qiáng)調(diào)缝左,本文的協(xié)程樣例代碼,是通過容器收集Job對(duì)象的方式來進(jìn)行的浓若,事實(shí)上渺杉,這是個(gè)非常不合理的做法

一來挪钓,每次書寫啟動(dòng)協(xié)程方法的時(shí)候都要注意不能遺漏Job的收集是越,非常麻煩;

二來碌上,在協(xié)程執(zhí)行完成以后倚评,由于Job對(duì)象仍在容器當(dāng)中有引用浦徊,所以協(xié)程執(zhí)行完后并不能及時(shí)回收對(duì)象所占用內(nèi)存;

而這兩個(gè)問題天梧,通過協(xié)程作用域可以完美地自動(dòng)解決盔性!所以,接下來呢岗,才是認(rèn)識(shí)協(xié)程作用域的正確學(xué)習(xí)時(shí)機(jī)冕香!

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(一)協(xié)程啟動(dòng)

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(二)線程切換

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(三)初遇協(xié)程取消(本文)

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(四)協(xié)程作用域

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(五)再遇協(xié)程取消

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(六)初識(shí)掛起

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(七)初識(shí)結(jié)構(gòu)化

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(八)初識(shí)協(xié)程異常

一學(xué)就會(huì)的協(xié)程使用——基礎(chǔ)篇(九)異常與supervisor

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
禁止轉(zhuǎn)載,如需轉(zhuǎn)載請(qǐng)通過簡(jiǎn)信或評(píng)論聯(lián)系作者后豫。
  • 序言:七十年代末悉尾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挫酿,更是在濱河造成了極大的恐慌构眯,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件饭豹,死亡現(xiàn)場(chǎng)離奇詭異鸵赖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)拄衰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門它褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人翘悉,你說我怎么就攤上這事茫打。” “怎么了妖混?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵老赤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我制市,道長(zhǎng)抬旺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任祥楣,我火速辦了婚禮开财,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘误褪。我一直安慰自己责鳍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布兽间。 她就那樣靜靜地躺著历葛,像睡著了一般。 火紅的嫁衣襯著肌膚如雪嘀略。 梳的紋絲不亂的頭發(fā)上恤溶,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天乓诽,我揣著相機(jī)與錄音,去河邊找鬼咒程。 笑死问裕,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的孵坚。 我是一名探鬼主播粮宛,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼卖宠!你這毒婦竟也來了巍杈?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤扛伍,失蹤者是張志新(化名)和其女友劉穎筷畦,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刺洒,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鳖宾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了逆航。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片鼎文。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖因俐,靈堂內(nèi)的尸體忽然破棺而出拇惋,到底是詐尸還是另有隱情,我是刑警寧澤抹剩,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布撑帖,位于F島的核電站,受9級(jí)特大地震影響澳眷,放射性物質(zhì)發(fā)生泄漏胡嘿。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一钳踊、第九天 我趴在偏房一處隱蔽的房頂上張望衷敌。 院中可真熱鬧,春花似錦箍土、人聲如沸逢享。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至弓柱,卻和暖如春沟堡,著一層夾襖步出監(jiān)牢的瞬間侧但,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工航罗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留禀横,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓粥血,卻偏偏與公主長(zhǎng)得像柏锄,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子复亏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容