欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Kotlin?協(xié)程的取消機(jī)制詳細(xì)解讀

 更新時(shí)間:2022年10月25日 11:25:53   作者:自動(dòng)化BUG制造器  
這篇文章主要為大家介紹了Kotlin?協(xié)程的取消機(jī)制詳細(xì)解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

引言

在 Java 語言中提供了線程中斷的能力,但并不是所有的線程都可以中斷的,因?yàn)?interrupt 方法并不是真正的終止線程,而是將一個(gè)標(biāo)志位標(biāo)記為中斷狀態(tài),當(dāng)運(yùn)行到下一次中斷標(biāo)志位檢查時(shí),才能觸發(fā)終止線程。

但無論如何,終止線程是一個(gè)糟糕的方案,因?yàn)樵诰€程的銷毀和重建,是要消耗系統(tǒng)資源的,造成了不必要的開銷。Kotlin 協(xié)程提供了更優(yōu)雅的取消機(jī)制,這也是協(xié)程比較核心的功能之一。

協(xié)程的狀態(tài)

在了解取消機(jī)制之前我們需要知道一些關(guān)于 Job 狀態(tài)的內(nèi)容:

StateisActive(是否活躍)isCompleted(是否完成)isCancelled(是否取消)
New (可選初始狀態(tài))falsefalsefalse
Active (默認(rèn)初始狀態(tài))truefalsefalse
Completing (短暫態(tài))truefalsefalse
Cancelling (短暫態(tài))falsefalsetrue
Cancelled (完成態(tài))falsetruetrue
Completed (完成態(tài))falsetruefalse

可以看出,在完成和取消的過程中,會(huì)經(jīng)過一個(gè)短暫的進(jìn)行中的狀態(tài),然后才變成已完成/已取消。

在這里只關(guān)注一下取消相關(guān)的狀態(tài):

  • Cancelling
  • 拋出異常的 Job 會(huì)導(dǎo)致其進(jìn)入 Cancelling 狀態(tài),也可以使用 cancel 方法來隨時(shí)取消 Job 使其立即轉(zhuǎn)換為 Cancelling 狀態(tài)。
  • Cancelled
  • 當(dāng)它遞歸取消子項(xiàng),并等待所有的子項(xiàng)都取消后,該 Job 會(huì)進(jìn)入 Cancelled 狀態(tài)。

取消協(xié)程的用法

協(xié)程在代碼中抽象的類型是 Job , 下面是一個(gè)官方的代碼示例,用來展示如何取消協(xié)程的執(zhí)行:

suspend fun main(): Unit = coroutineScope {
    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.")
}

它的輸出是:

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.

一旦 mian 方法中調(diào)用了 job.cancel() ,我們就看不到其他協(xié)程的任何輸出,因?yàn)樗驯蝗∠恕?/p>

協(xié)程取消的有效性

協(xié)程代碼必須通過與掛起函數(shù)的配合才能被取消。kotlinx.coroutines 中所有掛起函數(shù)(帶有 suspend 關(guān)鍵字函數(shù))都是可以被取消的。suspend 函數(shù)會(huì)檢查協(xié)程是否需要取消并在取消時(shí)拋出 CancellationException 。

但是,如果協(xié)程在運(yùn)行過程中沒有掛起點(diǎn),則不能取消協(xié)程,如下例所示:

suspend fun main(): Unit = coroutineScope {
    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.")
}

在這個(gè) job 中,并沒有執(zhí)行任何 suspend 函數(shù),所以在執(zhí)行過程中并沒有對(duì)協(xié)程是否需要取消進(jìn)行檢查,自然也就無法觸發(fā)取消。

同樣的問題也可以在通過 捕獲 CancellationException 并且不拋出的情況下 觀察到:

suspend fun main(): Unit = coroutineScope {
    val job = launch(Dispatchers.Default) {
        repeat(5) { i ->
            try {
                // print a message twice a second
                println("job: I'm sleeping $i ...")
                delay(500)
            } catch (e: Exception) {
                // log the exception
                println(e)
            }
        }
    }
    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.")
}

打印結(jié)果是:

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@614acfe9
job: I'm sleeping 3 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@614acfe9
job: I'm sleeping 4 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@614acfe9
main: Now I can quit.

從打印結(jié)果來看,循環(huán) 5 次全部執(zhí)行了,好像取消并沒有起到作用。但實(shí)際上不是這樣的,為了便于觀察加上時(shí)間戳:

1665217217682: job: I'm sleeping 0 ...
1665217218196: job: I'm sleeping 1 ...
1665217218697: job: I'm sleeping 2 ...
1665217218996: main: I'm tired of waiting!
1665217219000: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@3a1efc0d
1665217219000: job: I'm sleeping 3 ...
1665217219000: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@3a1efc0d
1665217219000: job: I'm sleeping 4 ...
1665217219000: kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@3a1efc0d
1665217219001: main: Now I can quit.

加上時(shí)間可以看出,拋出第一次異常后的兩次循環(huán)和異常捕獲都是在同一瞬間完成的。這說明了捕獲到異常后,仍然會(huì)執(zhí)行代碼,但是所有的 delay 方法都沒有生效,即該 Job 的所有子 Job 都失效了。但該 Job 仍在繼續(xù)循環(huán)打印。原因是,父 Job 會(huì)等所有子 Job 處理結(jié)束后才能完成取消。

而如果我們不使用 try-catch 呢?

suspend fun main(): Unit = coroutineScope {
    val job = launch(Dispatchers.Default) {
        repeat(5) { i ->
            // print a message twice a second
            println("job: I'm sleeping $i ...")
            delay(500)
        }
    }
    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.")
}

打印結(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.

很順利的取消了,這是因?yàn)閰f(xié)程拋出 Exception 直接終止了。

注意協(xié)程拋出 CancellationException 并不會(huì)導(dǎo)致 App Crash 。

使用 try-catch 來捕獲 CancellationException 時(shí)需要注意,在掛起函數(shù)前的代碼邏輯仍會(huì)多次執(zhí)行,從而導(dǎo)致這部分代碼仿佛沒有被取消一樣。

如何寫出可以取消的代碼

有兩種方法可以使代碼是可取消的。第一種方法是定期調(diào)用掛起函數(shù),檢查是否取消,就是上面的例子中的方法;另一個(gè)是顯式檢查取消狀態(tài):

suspend fun main(): Unit = coroutineScope {
    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.")
}

將上面的循環(huán) 5 次通過使用 while (isActive) 進(jìn)行替換,實(shí)現(xiàn)顯示檢查取消的代碼。isActive 是通過 CoroutineScope 對(duì)象在協(xié)程內(nèi)部可用的擴(kuò)展屬性。

在 finally 中釋放資源

在前面的例子中我們使用 try-catch 捕獲 CancellationException 發(fā)現(xiàn)會(huì)產(chǎn)生父協(xié)程等待所有子協(xié)程完成后才能完成,所以建議不用 try-catch 而是 try{…} finally{…} ,讓父協(xié)程在被取消時(shí)正常執(zhí)行終結(jié)操作:

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.")

join 和 cancelAndJoin 都要等待所有終結(jié)操作完成,所以上面的例子產(chǎn)生了以下輸出:

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.

使用不可取消的 block

如果在在上面的示例的 finally 代碼塊中使用 suspend 函數(shù),會(huì)導(dǎo)致拋出 CancellationException 。

因?yàn)檫\(yùn)行這些代碼的協(xié)程已經(jīng)被取消了。通常情況下這不會(huì)有任何問題,然而,在極少數(shù)情況下,如果你需要在 finally 中使用一個(gè)掛起函數(shù),你可以通過使用 withContext(NonCancellable) { ... }

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.")

CancellationException

在上面的內(nèi)容中,我們知道協(xié)程的取消是通過拋出 CancellationException 來進(jìn)行的,神奇的是拋出 Exception 并沒有導(dǎo)致應(yīng)用程序 Crash 。

CancellationException 的真實(shí)實(shí)現(xiàn)是 j.u.c. 中的 CancellationException :

public actual typealias CancellationException = java.util.concurrent.CancellationException

如果協(xié)程的 Job 被取消,則由可取消的掛起函數(shù)拋出 CancellationException 。它表示協(xié)程的正常取消。在默認(rèn)的 CoroutineExceptionHandler 下,它不會(huì)打印到控制臺(tái)/日志。

上面引用了這個(gè)類的注釋,看來處理拋出異常的邏輯在 CoroutineExceptionHandler 中:

public interface CoroutineExceptionHandler : CoroutineContext.Element {
    /**
     * Key for [CoroutineExceptionHandler] instance in the coroutine context.
     */
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>
    /**
     * Handles uncaught [exception] in the given [context]. It is invoked
     * if coroutine has an uncaught exception.
     */
    public fun handleException(context: CoroutineContext, exception: Throwable)
}

通常,未捕獲的 Exception 只能由使用協(xié)程構(gòu)建器的根協(xié)程產(chǎn)生。所有子協(xié)程都將異常的處理委托給他們的父協(xié)程,父協(xié)程也委托給它自身的父協(xié)程,直到委托給根協(xié)程處理。所以在子協(xié)程中的 CoroutineExceptionHandler 永遠(yuǎn)不會(huì)被使用。

使用 SupervisorJob 運(yùn)行的協(xié)程不會(huì)將異常傳遞給它們的父協(xié)程,SupervisorJob 被視為根協(xié)程。

使用 async 創(chuàng)建的協(xié)程總是捕獲它的所有異常通過結(jié)果 Deferred 對(duì)象回調(diào)出去,因此它不能導(dǎo)致未捕獲的異常。

CoroutineExceptionHandler 用于記錄異常、顯示某種類型的錯(cuò)誤消息、終止和/或重新啟動(dòng)應(yīng)用程序。

如果需要在代碼的特定部分處理異常,建議在協(xié)程中的相應(yīng)代碼周圍使用 try-catch。通過這種方式,您可以阻止異常協(xié)程的完成(異?,F(xiàn)在被捕獲),重試操作,和/或采取其他任意操作。 這也就是我們前面論證的在協(xié)程中使用 try-catch 導(dǎo)致的取消失效。

默認(rèn)情況下,如果協(xié)程沒有配置用于處理異常的 Handler ,未捕獲的異常將按以下方式處理:

如果 exception 是 CancellationException ,那么它將被忽略(因?yàn)檫@是取消正在運(yùn)行的協(xié)程的假定機(jī)制)。

其他情況:

  • 如果上下文中有一個(gè) Job,那么調(diào)用 job.cancel()
  • 否則,通過 ServiceLoader 找到的 CoroutineExceptionHandler 的所有實(shí)例并調(diào)用當(dāng)前線程的 Thread.uncaughtExceptionHandler 來處理異常。

超時(shí)取消

取消協(xié)程執(zhí)行的最合適的應(yīng)用場景是它的執(zhí)行時(shí)間超過了規(guī)定的最大時(shí)間時(shí)自動(dòng)取消任務(wù)。在 Kotlin 協(xié)程庫中提供了 withTimeout 方法來實(shí)現(xiàn)這個(gè)功能:

withTimeout(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
}

執(zhí)行結(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

TimeoutCancellationException 是 CancellationException 的子類,TimeoutCancellationException 通過 withTimeout 函數(shù)拋出。

在本例中,我們?cè)趍ain函數(shù)中使用了withTimeout ,運(yùn)行過程中會(huì)導(dǎo)致 Crash 。

有兩種解決辦法,就是使用 try{…} catch (e: TimeoutCancellationException){…} 代碼塊;另一種辦法是使用在超時(shí)的情況下不是拋出異常而是返回 null 的 withTimeoutOrNull 函數(shù):

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")

打印結(jié)果:

I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null

異步的超時(shí)和資源

withTimeout 中的超時(shí)事件相對(duì)于在其塊中運(yùn)行的代碼是異步的,并且可能在任何時(shí)間發(fā)生,甚至在從超時(shí)塊內(nèi)部返回之前。如果你在塊內(nèi)部打開或獲取一些資源,需要關(guān)閉或釋放到塊外部。

例如,在這里,我們用 Resource 類模擬一個(gè)可關(guān)閉資源,它只是通過對(duì)獲得的計(jì)數(shù)器遞增,并對(duì)該計(jì)數(shù)器從其關(guān)閉函數(shù)遞減來跟蹤創(chuàng)建次數(shù)。讓我們用小超時(shí)運(yùn)行大量的協(xié)程,嘗試在一段延遲后從withTimeout塊內(nèi)部獲取這個(gè)資源,并從外部釋放它。

var acquired = 0
class Resource {
    init { acquired++ } // Acquire the resource
    fun close() { acquired-- } // Release the resource
}
fun main() {
    runBlocking {
        repeat(100_000) { // Launch 100K coroutines
            launch { 
                val resource = withTimeout(60) { // Timeout of 60 ms
                    delay(50) // Delay for 50 ms
                    Resource() // Acquire a resource and return it from withTimeout block     
                }
                resource.close() // Release the resource
            }
        }
    }
    // Outside of runBlocking all coroutines have completed
    println(acquired) // Print the number of resources still acquired
}

如果運(yùn)行上面的代碼,您將看到它并不總是打印 0,盡管它可能取決于您的機(jī)器的時(shí)間,在本例中您可能需要調(diào)整超時(shí)以實(shí)際看到非零值。

要解決這個(gè)問題,可以在變量中存儲(chǔ)對(duì)資源的引用,而不是從withTimeout塊返回它。

fun main() {
    runBlocking {
        repeat(100_000) { // Launch 100K coroutines
            launch {
                var resource: Resource? = null // Not acquired yet
                try {
                    withTimeout(60) { // Timeout of 60 ms
                        delay(50) // Delay for 50 ms
                        resource = Resource() // Store a resource to the variable if acquired
                    }
                    // We can do something else with the resource here
                } finally {
                    resource?.close() // Release the resource if it was acquired
                }
            }
        }
    }
// Outside of runBlocking all coroutines have completed
    println(acquired) // Print the number of resources still acquired
}

這樣這個(gè)例子總是輸出0。資源不會(huì)泄漏。

取消檢查的底層原理

在探索協(xié)程取消的有效性時(shí),我們知道協(xié)程代碼必須通過與掛起函數(shù)的配合才能被取消。

kotlinx.coroutines 中所有掛起函數(shù)(帶有 suspend 關(guān)鍵字函數(shù))都是可以被取消的。suspend 函數(shù)會(huì)檢查協(xié)程是否需要取消并在取消時(shí)拋出 CancellationException 。

關(guān)于協(xié)程的取消機(jī)制,很明顯和 suspend 關(guān)鍵字有關(guān)。為了測試 suspend 關(guān)鍵字的作用,實(shí)現(xiàn)下面的代碼:

class Solution {
    suspend fun func(): String {
        return "測試 suspend 關(guān)鍵字"
    }
}

作為對(duì)照組,另一個(gè)是不加 suspend 關(guān)鍵字的 func 方法:

class Solution {
    fun func(): String {
        return "測試 suspend 關(guān)鍵字"
    }
}

兩者反編譯成 Java :

// 普通的方法
public final class Solution {
    public static final int $stable = LiveLiterals$SolutionKt.INSTANCE.Int$class-Solution();
    @NotNull
    public final String func() {
        return LiveLiterals$SolutionKt.INSTANCE.String$fun-func$class-Solution();
    }
}
// 帶有 suspend 關(guān)鍵字的方法
public final class Solution {
    public static final int $stable = LiveLiterals$SolutionKt.INSTANCE.Int$class-Solution();
    @Nullable
    public final Object func(@NotNull Continuation<? super String> $completion) {
        return LiveLiterals$SolutionKt.INSTANCE.String$fun-func$class-Solution();
    }
}

suspend 關(guān)鍵字修飾的方法反編譯后默認(rèn)生成了帶有 Continuation 參數(shù)的方法。說明 suspend 關(guān)鍵字的玄機(jī)在 Continuation 類中。

Continuation 是 Kotlin 協(xié)程的核心思想 Continuation-Passing Style 的實(shí)現(xiàn)。原理參考簡述協(xié)程的底層實(shí)現(xiàn)原理

通過在普通函數(shù)的參數(shù)中增加一個(gè) Continuation 參數(shù),這個(gè) continuation 的性質(zhì)類似于一個(gè) lambda 對(duì)象,將方法的返回值類型傳遞到這個(gè) lambda 代碼塊中。

什么意思呢?就是本來這個(gè)方法的返回類型直接 return 出來的:

val a: String = func()
print(a)

而經(jīng)過 suspend 修飾,代碼變成了這個(gè)樣子:

func { a ->
    print(a)
}

Kotlin 協(xié)程就是通過這樣的包裝,將比如 launch 方法,實(shí)際上是 launch 最后一個(gè)參數(shù)接收的是 lambda 參數(shù)。也就是把外部邏輯傳遞給函數(shù)內(nèi)部執(zhí)行。

回過頭來再來理解 suspend 關(guān)鍵字,我們知道帶有 suspend 關(guān)鍵字的方法會(huì)對(duì)協(xié)程的取消進(jìn)行檢查,從而取消協(xié)程的執(zhí)行。從這個(gè)能力上來看,我理解他應(yīng)該會(huì)自動(dòng)生成類似下面的邏輯代碼:

生成的函數(shù) {
    if(!當(dāng)前協(xié)程.isActive) {
        throw CancellationException()
    }
    // ... 這里是函數(shù)真實(shí)邏輯
}

suspend 修飾的函數(shù),會(huì)自動(dòng)生成一個(gè)掛起點(diǎn),來檢查協(xié)程是否應(yīng)該被掛起。

顯然 Continuation 中聲明的函數(shù)也證實(shí)了掛起的功能:

public interface Continuation<in T> {
    /**
     * The context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext
    /**
     * 恢復(fù)相應(yīng)協(xié)程的執(zhí)行,將成功或失敗的結(jié)果作為最后一個(gè)掛起點(diǎn)的返回值傳遞。
     */
    public fun resumeWith(result: Result<T>)
}

協(xié)程本質(zhì)上是產(chǎn)生了一個(gè) switch 語句,每個(gè)掛起點(diǎn)之間的邏輯都是一個(gè) case 分支的邏輯。參考 協(xié)程是如何實(shí)現(xiàn)的 中的例子:

        Function1 lambda = (Function1)(new Function1((Continuation)null) {
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
                byte text;
                @BlockTag1: {
                    Object result;
                    @BlockTag2: {
                        result = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                        switch(this.label) {
                            case 0:
                                ResultKt.throwOnFailure($result);
                                this.label = 1;
                                if (SuspendTestKt.dummy(this) == result) {
                                    return result;
                                }
                                break;
                            case 1:
                                ResultKt.throwOnFailure($result);
                                break;
                            case 2:
                                ResultKt.throwOnFailure($result);
                                break @BlockTag2;
                            case 3:
                                ResultKt.throwOnFailure($result);
                                break @BlockTag1;
                            default:
                                throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                        }
                        text = 1;
                        System.out.println(text);
                        this.label = 2;
                        if (SuspendTestKt.dummy(this) == result) {
                            return result;
                        }
                    }
                    text = 2;
                    System.out.println(text);
                    this.label = 3;
                    if (SuspendTestKt.dummy(this) == result) {
                        return result;
                    }
                }
                text = 3;
                System.out.println(text);
                return Unit.INSTANCE;
            }
            @NotNull
            public final Continuation create(@NotNull Continuation completion) {
                Intrinsics.checkNotNullParameter(completion, "completion");
                Function1 funcation = new <anonymous constructor>(completion);
                return funcation;
            }
            public final Object invoke(Object object) {
                return ((<undefinedtype>)this.create((Continuation)object)).invokeSuspend(Unit.INSTANCE);
            }
        });

可以看出,在每個(gè)分支都會(huì)執(zhí)行一次 ResultKt.throwOnFailure($result); ,從名字上就知道,這就是檢查是否需要取消并拋出異常的代碼所在:

@PublishedApi
@SinceKotlin("1.3")
internal fun Result<*>.throwOnFailure() {
    if (value is Result.Failure) throw value.exception
}

這里的 Result 類是一個(gè)包裝類,它將成功的結(jié)果封裝為類型 T 的值,或?qū)⑹〉慕Y(jié)果封裝為帶有任意Throwable異常的值。

        @Suppress("INAPPLICABLE_JVM_NAME")
        @InlineOnly
        @JvmName("success")
        public inline fun <T> success(value: T): Result<T> =
            Result(value)
        /**
         * Returns an instance that encapsulates the given [Throwable] [exception] as failure.
         */
        @Suppress("INAPPLICABLE_JVM_NAME")
        @InlineOnly
        @JvmName("failure")
        public inline fun <T> failure(exception: Throwable): Result<T> =
            Result(createFailure(exception))

成功和失敗的方法類型是不一樣的,證實(shí)了這一點(diǎn),success 方法接收類型為 T 的參數(shù);failure 接收 Throwable 類型的參數(shù)。

到這里 suspend 方法掛起的原理就明了了:在協(xié)程的狀態(tài)機(jī)中,通過掛起點(diǎn)會(huì)分割出不同的狀態(tài),對(duì)每一個(gè)狀態(tài),會(huì)先進(jìn)行掛起結(jié)果的檢查。 這會(huì)導(dǎo)致以下結(jié)果:

  • 協(xié)程的取消機(jī)制是通過掛起函數(shù)的掛起點(diǎn)檢查來進(jìn)行取消檢查的。證實(shí)了為什么如果沒有 suspend 函數(shù)(本質(zhì)是掛起點(diǎn)),協(xié)程的取消就不會(huì)生效。
  • 協(xié)程的取消機(jī)制是需要函數(shù)合作的,就是通過 suspend 函數(shù)來增加取消檢查的時(shí)機(jī)。
  • 父協(xié)程會(huì)執(zhí)行完所有的子協(xié)程(掛起函數(shù)),因?yàn)榇a的本質(zhì)是一個(gè)循環(huán)執(zhí)行 switch 語句,當(dāng)一個(gè)子協(xié)程(或掛起函數(shù))執(zhí)行結(jié)束,會(huì)繼續(xù)執(zhí)行到下一個(gè)分支。但是最后一個(gè)掛起點(diǎn)后續(xù)的代碼并不會(huì)被執(zhí)行,因?yàn)樽詈笠粋€(gè)掛起點(diǎn)檢查到失敗,不會(huì)繼續(xù)跳到最后的 label 分支。

以上就是Kotlin 協(xié)程的取消機(jī)制詳細(xì)解讀的詳細(xì)內(nèi)容,更多關(guān)于Kotlin 協(xié)程取消機(jī)制的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論