【譯】kotlin 協(xié)程官方文檔(2)- 取消和超時

最近一直在了解關(guān)于 Kotlin協(xié)程 的知識,那最好的學(xué)習(xí)資料自然是官方提供的學(xué)習(xí)文檔了六敬,看了看后我就萌生了翻譯官方文檔的想法碘赖。前后花了要接近一個月時間,一共九篇文章外构,在這里也分享出來普泡,希望對讀者有所幫助。個人知識所限典勇,有些翻譯得不是太順暢劫哼,也希望讀者能提出意見

協(xié)程官方文檔:coroutines-guide

本節(jié)討論協(xié)程的取消和超時

一、取消協(xié)程執(zhí)行

在一個長時間運(yùn)行的應(yīng)用程序中割笙,我們可能需要對協(xié)程進(jìn)行細(xì)粒度控制。例如,用戶可能關(guān)閉了啟動了協(xié)程的頁面伤溉,現(xiàn)在不再需要其運(yùn)行結(jié)果般码,此時就應(yīng)該主動取消協(xié)程。launch 函數(shù)的返回值 Job 對象就可用于取消正在運(yùn)行的協(xié)程

import kotlinx.coroutines.*

fun main() = runBlocking {
//sampleStart
    val job = launch {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    job.join() // waits for job's completion 
    println("main: Now I can quit.")
    //sampleEnd    
}

運(yùn)行結(jié)果

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.

只要 main 函數(shù)調(diào)用了 job.cancel乱顾,我們就看不到 job 協(xié)程的任何輸出了板祝,因?yàn)樗驯蝗∠_€有一個 Job 的擴(kuò)展函數(shù) cancelAndJoin 走净,它結(jié)合了 canceljoin 的調(diào)用券时。

cancel() 函數(shù)用于取消協(xié)程,join() 函數(shù)用于阻塞等待協(xié)程執(zhí)行結(jié)束伏伯。之所以連續(xù)調(diào)用這兩個方法橘洞,是因?yàn)?cancel() 函數(shù)調(diào)用后會馬上返回而不是等待協(xié)程結(jié)束后再返回,所以此時協(xié)程不一定是馬上就停止了说搅,為了確保協(xié)程執(zhí)行結(jié)束后再執(zhí)行后續(xù)代碼炸枣,此時就需要調(diào)用 join() 方法來阻塞等待∨螅可以通過調(diào)用 Job 的擴(kuò)展函數(shù) cancelAndJoin() 來完成相同操作

public suspend fun Job.cancelAndJoin() {
    cancel()
    return join()
}

二适肠、取消操作是協(xié)作完成的

協(xié)程的取消操作是協(xié)作(cooperative)完成的,協(xié)程必須協(xié)作才能取消候引。kotlinx.coroutines 中的所有掛起函數(shù)都是可取消的侯养,它們在運(yùn)行時會檢查協(xié)程是否被取消了,并在取消時拋出 CancellationException 澄干。但是沸毁,如果協(xié)程正在執(zhí)行計(jì)算任務(wù),并且未檢查是否已處于取消狀態(tài)的話傻寂,則無法取消協(xié)程息尺,如以下示例所示:

import kotlinx.coroutines.*

fun main() = runBlocking {
    //sampleStart
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // computation loop, just wastes CPU
            // print a message twice a second
            if (System.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.")
    //sampleEnd    
}

運(yùn)行代碼可以看到即使在 cancel 之后協(xié)程 job 也會繼續(xù)打印 "I'm sleeping" ,直到 Job 在迭代五次后(運(yùn)行條件不再成立)自行結(jié)束

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

三疾掰、使計(jì)算代碼可取消

有兩種方法可以使計(jì)算類型的代碼可以被取消搂誉。第一種方法是定期調(diào)用一個掛起函數(shù)來檢查取消操作,yieid() 函數(shù)是一個很好的選擇静檬。另一個方法是顯示檢查取消操作炭懊。讓我們來試試后一種方法

使用 while (isActive) 替換前面例子中的 while (i < 5)

import kotlinx.coroutines.*

fun main() = runBlocking {
    //sampleStart
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // cancellable computation loop
            // print a message twice a second
            if (System.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.")
    //sampleEnd    
}

如你所見,現(xiàn)在這個循環(huán)被取消了拂檩。isActive 是一個可通過 CoroutineScope 對象在協(xié)程內(nèi)部使用的擴(kuò)展屬性

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.

四侮腹、用 finally 關(guān)閉資源

可取消的掛起函數(shù)在取消時會拋出 CancellationException,可以用常用的方式來處理這種情況稻励。例如父阻,try {...} finally {...} 表達(dá)式和 kotlin 的 use 函數(shù)都可用于在取消協(xié)程時執(zhí)行回收操作

import kotlinx.coroutines.*

fun main() = runBlocking {
    //sampleStart
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("job: I'm running finally")
        }
    }
    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.")
    //sampleEnd    
}

join() 和 cancelAndJoin() 兩個函數(shù)都會等待所有回收操作完成后再繼續(xù)執(zhí)行之后的代碼愈涩,因此上面的示例生成以下輸出:

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

五、運(yùn)行不可取消的代碼塊

如果在上一個示例中的 finally 塊中使用掛起函數(shù)加矛,將會導(dǎo)致拋出 CancellationException履婉,因?yàn)榇藭r協(xié)程已經(jīng)被取消了(例如,在 finally 中先調(diào)用 delay(1000L) 函數(shù)斟览,將導(dǎo)致之后的輸出語句不執(zhí)行)毁腿。通常這并不是什么問題,因?yàn)樗行阅芰己玫年P(guān)閉操作(關(guān)閉文件苛茂、取消作業(yè)已烤、關(guān)閉任何類型的通信通道等)通常都是非阻塞的,且不涉及任何掛起函數(shù)妓羊。但是胯究,在極少數(shù)情況下,當(dāng)需要在取消的協(xié)程中調(diào)用掛起函數(shù)時侍瑟,可以使用 withContext 函數(shù)和 NonCancellable 上下文將相應(yīng)的代碼包裝在 withContext(NonCancellable) {...} 代碼塊中唐片,如下例所示:

import kotlinx.coroutines.*

fun main() = runBlocking {
    //sampleStart
    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.")
    //sampleEnd    
}

此時,即使在 finally 代碼塊中調(diào)用了掛起函數(shù)涨颜,其也將正常生效费韭,且之后的輸出語句也會正常輸出

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm running finally
job: And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.

六、超時

大多數(shù)情況下庭瑰,我們會主動取消協(xié)程的原因是由于其執(zhí)行時間已超出預(yù)估的最長時間星持。雖然我們可以手動跟蹤對相應(yīng) Job 的引用,并在超時后取消 Job弹灭,但官方也提供了 withTimeout 函數(shù)來完成此類操作督暂。看一下示例:

import kotlinx.coroutines.*

fun main() = runBlocking {
    //sampleStart
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    //sampleEnd
}

運(yùn)行結(jié)果:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms

withTimeout 引發(fā)的 TimeoutCancellationException 是 CancellationException 的子類穷吮。之前我們從未在控制臺上看過 CancellationException 這類異常的堆棧信息逻翁。這是因?yàn)閷τ谝粋€已取消的協(xié)程來說,CancellationException 被認(rèn)為是觸發(fā)協(xié)程結(jié)束的正常原因捡鱼。但是八回,在這個例子中,我們在主函數(shù)中使用了 withTimeout 函數(shù)驾诈,該函數(shù)會主動拋出 TimeoutCancellationException

你可以通過使用 try{...}catch(e:TimeoutCancellationException){...} 代碼塊來對任何情況下的超時操作執(zhí)行某些特定的附加操作缠诅,或者通過使用 withTimeoutOrNull 函數(shù)以便在超時時返回 null 而不是拋出異常

import kotlinx.coroutines.*

fun main() = runBlocking {
    //sampleStart
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
        "Done" // will get cancelled before it produces this result
    }
    println("Result is $result")
    //sampleEnd
}

此時將不會打印出異常信息

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市乍迄,隨后出現(xiàn)的幾起案子管引,更是在濱河造成了極大的恐慌,老刑警劉巖闯两,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件褥伴,死亡現(xiàn)場離奇詭異谅将,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)噩翠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門戏自,熙熙樓的掌柜王于貴愁眉苦臉地迎上來邦投,“玉大人伤锚,你說我怎么就攤上這事≈疽拢” “怎么了屯援?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長念脯。 經(jīng)常有香客問我狞洋,道長,這世上最難降的妖魔是什么绿店? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任吉懊,我火速辦了婚禮,結(jié)果婚禮上假勿,老公的妹妹穿的比我還像新娘借嗽。我一直安慰自己,他們只是感情好转培,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布恶导。 她就那樣靜靜地躺著,像睡著了一般浸须。 火紅的嫁衣襯著肌膚如雪惨寿。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天删窒,我揣著相機(jī)與錄音裂垦,去河邊找鬼。 笑死肌索,一個胖子當(dāng)著我的面吹牛蕉拢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播驶社,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼企量,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了亡电?” 一聲冷哼從身側(cè)響起届巩,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎份乒,沒想到半個月后恕汇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體腕唧,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年瘾英,在試婚紗的時候發(fā)現(xiàn)自己被綠了枣接。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡缺谴,死狀恐怖但惶,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情湿蛔,我是刑警寧澤膀曾,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站阳啥,受9級特大地震影響添谊,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜察迟,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一斩狱、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扎瓶,春花似錦所踊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至乍赫,卻和暖如春瓣蛀,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雷厂。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工惋增, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人改鲫。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓诈皿,卻偏偏與公主長得像,于是被迫代替她去往敵國和親像棘。 傳聞我的和親對象是個殘疾皇子稽亏,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

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