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

Kotlin 協(xié)程的異常處理準(zhǔn)則

 更新時間:2024年01月10日 10:32:43   作者:xiangxiongfly915  
協(xié)程是互相協(xié)作的程序,協(xié)程是結(jié)構(gòu)化的,正是因為協(xié)程的這兩個特點(diǎn),導(dǎo)致它和 Java 的異常處理機(jī)制不一樣,這篇文章重點(diǎn)給大家介紹Kotlin 協(xié)程的異常處理準(zhǔn)則,感興趣的朋友一起看看吧

Kotlin 協(xié)程的異常處理

概述

協(xié)程是互相協(xié)作的程序,協(xié)程是結(jié)構(gòu)化的。

正是因為協(xié)程的這兩個特點(diǎn),導(dǎo)致它和 Java 的異常處理機(jī)制不一樣。如果將 Java 的異常處理機(jī)制照搬到Kotlin協(xié)程中,會遇到很多問題,如:協(xié)程無法取消、try-catch不起作用等。

Kotlin協(xié)程中的異常主要分兩大類

  • 協(xié)程取消異常(CancellationException)
  • 其他異常

異常處理六大準(zhǔn)則

  • 協(xié)程的取消需要內(nèi)部配合。
  • 不要打破協(xié)程的父子結(jié)構(gòu)。
  • 捕獲 CancellationException 異常后,需要考慮是否重新拋出來。
  • 不要用 try-catch 直接包裹 launch、async。
  • 使用 SurpervisorJob 控制異常傳播的范圍。
  • 使用 CoroutineExceptionHandler 處理復(fù)雜結(jié)構(gòu)的協(xié)程異常,僅在頂層協(xié)程中起作用。

核心理念:協(xié)程是結(jié)構(gòu)化的,異常傳播也是結(jié)構(gòu)化的。

準(zhǔn)則一:協(xié)程的取消需要內(nèi)部配合

協(xié)程任務(wù)被取消時,它的內(nèi)部會產(chǎn)生一個 CancellationException 異常,協(xié)程的結(jié)構(gòu)化并發(fā)的特點(diǎn):如果取消了父協(xié)程,則子協(xié)程也會跟著取消。

問題:cancel不被響應(yīng)

fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        var i = 0
        while (true) {
            Thread.sleep(500L)
            i++
            println("i: $i")
        }
    }
    delay(200L)
    job.cancel()
    job.join()
    println("End")
}
/*
輸出信息:
i: 1
i: 2
i: 3
i: 4
// 不會停止,一直打印輸出
 */

原因:協(xié)程是相互協(xié)作的程序,因此協(xié)程任務(wù)的取消也需要相互協(xié)作。協(xié)程外部取消,協(xié)程內(nèi)部需要做出相應(yīng)。

解決:使用 isActive 判斷是否處于活躍狀態(tài)

使用 isActive 判斷協(xié)程的活躍狀態(tài)。

fun main() = runBlocking {
    val job = launch(Dispatchers.Default) {
        var i = 0
        //       關(guān)鍵
        //        ↓
        while (isActive) {
            Thread.sleep(500L)
            i++
            println("i: $i")
        }
    }
    delay(200L)
    job.cancel()
    job.join()
    println("End")
}
/*
輸出信息:
i: 1
End
 */

準(zhǔn)則二:不要打破協(xié)程的父子結(jié)構(gòu)

問題:子協(xié)程不會跟隨父協(xié)程一起取消

val fixedDispatcher = Executors.newFixedThreadPool(2) {
    Thread(it, "MyFixedThread")
}.asCoroutineDispatcher()
fun main() = runBlocking {
    // 父協(xié)程
    val parentJob = launch(fixedDispatcher) {
        //子協(xié)程1
        launch(Job()) {
            var i = 0
            while (isActive) {
                Thread.sleep(500L)
                i++
                println("子協(xié)程1 i:$i")
            }
        }
        //子協(xié)程2
        launch {
            var i = 0
            while (isActive) {
                Thread.sleep(500L)
                i++
                println("子協(xié)程2 i:$i")
            }
        }
    }
    delay(1000L)
    parentJob.cancel()
    parentJob.join()
    println("End")
}
/*
輸出信息:
子協(xié)程1 i:1
子協(xié)程2 i:1
子協(xié)程2 i:2
子協(xié)程1 i:2
End
子協(xié)程1 i:3
子協(xié)程1 i:4
子協(xié)程1 i:5
// 子協(xié)程1一直在執(zhí)行,不會停下來
 */

原因:協(xié)程是結(jié)構(gòu)化的,取消啦父協(xié)程,子協(xié)程也會被取消。但是在這里“子協(xié)程1”不在 parentJob 的子協(xié)程,打破了原有的結(jié)構(gòu)化關(guān)系,當(dāng)調(diào)用 parentJob.cancel 時,“子協(xié)程1”就不會被取消了。

解決:不破壞父子結(jié)構(gòu)

“子協(xié)程1”不要傳入額外的 Job()。

fun main() = runBlocking {
    val parentJob = launch(fixedDispatcher) {
        launch {
            var i = 0
            while (isActive) {
                Thread.sleep(500L)
                i++
                println("子協(xié)程1:i= $i")
            }
        }
        launch {
            var i = 0
            while (isActive) {
                Thread.sleep(500L)
                i++
                println("子協(xié)程2:i= $i")
            }
        }
    }
    delay(2000L)
    parentJob.cancel()
    parentJob.join()
    println("end")
}
/*
輸出結(jié)果:
子協(xié)程1:i= 1
子協(xié)程2:i= 1
子協(xié)程2:i= 2
子協(xié)程1:i= 2
子協(xié)程1:i= 3
子協(xié)程2:i= 3
子協(xié)程1:i= 4
子協(xié)程2:i= 4
end
*/

準(zhǔn)則三:捕獲 CancellationException 需要重新拋出來

掛起函數(shù)可以自動響應(yīng)協(xié)程的取消

Kotlin 中的掛起函數(shù)是可以自動響應(yīng)協(xié)程的取消,如下中的 delay() 函數(shù)可以自動檢測當(dāng)前協(xié)程是否被取消,如果已經(jīng)取消了它就會拋出一個 CancellationException,從而終止當(dāng)前協(xié)程。

fun main() = runBlocking {
    // 父協(xié)程
    val parentJob = launch(Dispatchers.Default) {
        //子協(xié)程1
        launch {
            var i = 0
            while (true) {
                // 這里
                delay(500L)
                i++
                println("子協(xié)程1 i:$i")
            }
        }
        //子協(xié)程2
        launch {
            var i = 0
            while (true) {
                // 這里
                delay(500L)
                i++
                println("子協(xié)程2 i:$i")
            }
        }
    }
    delay(1000L)
    parentJob.cancel()
    parentJob.join()
    println("End")
}
/*
輸出信息:
子協(xié)程1 i:1
子協(xié)程2 i:1
子協(xié)程1 i:2
子協(xié)程2 i:2
End
 */
fun main() = runBlocking {
    // 父協(xié)程
    val parentJob = launch(Dispatchers.Default) {
        //子協(xié)程1
        launch {
            var i = 0
            while (true) {
                try {
                    delay(500L)
                } catch (e: CancellationException) {
                    println("捕獲CancellationException")
                    throw e
                }
                i++
                println("子協(xié)程1 i:$i")
            }
        }
        //子協(xié)程2
        launch {
            var i = 0
            while (true) {
                try {
                    delay(500L)
                } catch (e: CancellationException) {
                    println("捕獲CancellationException")
                    throw e
                }
                i++
                println("子協(xié)程2 i:$i")
            }
        }
    }
    delay(1000L)
    parentJob.cancel()
    parentJob.join()
    println("End")
}
/*
輸出信息:
子協(xié)程1 i:1
子協(xié)程2 i:1
捕獲CancellationException
捕獲CancellationException
End
 */

問題:捕獲 CancellationException 導(dǎo)致崩潰

fun main() = runBlocking {
    val parentJob = launch(Dispatchers.Default) {
        launch {
            var i = 0
            while (true) {
                try {
                    delay(500L)
                } catch (e: CancellationException) {
                    println("捕獲CancellationException異常")
                }
                i++
                println("子協(xié)程1 i= $i")
            }
        }
        launch {
            var i = 0
            while (true) {
                delay(500L)
                i++
                println("子協(xié)程2 i= $i")
            }
        }
    }
    delay(2000L)
    parentJob.cancel()
    parentJob.join()
    println("end")
}
/*
輸出信息:
子協(xié)程1 i= 1
子協(xié)程2 i= 1
子協(xié)程1 i= 2
子協(xié)程2 i= 2
子協(xié)程1 i= 3
子協(xié)程2 i= 3
捕獲CancellationException異常
...... //程序不會終止
*/

原因:當(dāng)捕獲到 CancellationException 以后,還需要將它重新拋出去,如果沒有拋出去則子協(xié)程將無法取消。

解決:需要重新拋出

重新拋出異常,執(zhí)行 throw e。

以上三條準(zhǔn)則,都是應(yīng)對 CancellationException 這個特殊異常的。

fun main() = runBlocking {
    val parentJob = launch(Dispatchers.Default) {
        launch {
            var i = 0
            while (true) {
                try {
                    delay(500L)
                } catch (e: CancellationException) {
                    println("捕獲CancellationException異常")
                    // 拋出異常
                    throw e
                }
                i++
                println("協(xié)程1 i= $i")
            }
        }
        launch {
            var i = 0
            while (true) {
                delay(500L)
                i++
                println("協(xié)程2 i= $i")
            }
        }
    }
    delay(2000L)
    parentJob.cancel()
    parentJob.join()
    println("end")
}
/*
輸出信息:
協(xié)程1 i= 1
協(xié)程2 i= 1
協(xié)程2 i= 2
協(xié)程1 i= 2
協(xié)程2 i= 3
協(xié)程1 i= 3
捕獲CancellationException異常
end
*/

準(zhǔn)則四:不要用try-catch直接包裹launch、async

問題:try-catch不起作用

fun main() = runBlocking {
    try {
        launch {
            delay(100L)
            1 / 0 //產(chǎn)生異常
        }
    } catch (e: ArithmeticException) {
        println("捕獲:$e")
    }
    delay(500L)
    println("end")
}
/*
輸出信息:
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/

原因:協(xié)程的代碼執(zhí)行順序與普通程序不一樣,當(dāng)協(xié)程執(zhí)行 1 / 0 時,程序?qū)嶋H已經(jīng)跳出 try-catch 的作用域了,所以直接使用 try-catch 包裹 launch、async 是沒有任何效果的。

解決:調(diào)整作用域

可以將 try-catch 移動到協(xié)程體內(nèi)部,這樣可以捕獲到異常了。

fun main() = runBlocking {
    launch {
        delay(100L)
        try {
            1 / 0 //產(chǎn)生異常
        } catch (e: ArithmeticException) {
            println("捕獲異常:$e")
        }
    }
    delay(500L)
    println("end")
}
/*
輸出信息:
捕獲異常:java.lang.ArithmeticException: / by zero
end
*/

準(zhǔn)則五:靈活使用SurpervisorJob

問題:子Job發(fā)生異常影響其他子Job

fun main() = runBlocking {
    launch {
        launch {
            1 / 0
            delay(100L)
            println("hello world 111")
        }
        launch {
            delay(200L)
            println("hello world 222")
        }
        launch {
            delay(300L)
            println("hello world 333")
        }
    }
    delay(1000L)
    println("end")
}
/*
輸出信息:
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/

原因:使用普通 Job 時,當(dāng)子Job發(fā)生異常時,會導(dǎo)致 parentJob 取消,從而導(dǎo)致其他子Job也受到牽連,這也是協(xié)程結(jié)構(gòu)化的體現(xiàn)。

解決:使用 SupervisorJob

SurpervisorJob 是 Job 的子類,SurpervisorJob 是一個種特殊的 Job,可以控制異常的傳播范圍,當(dāng)子Job發(fā)生異常時,其他的子Job不會受到影響。

將 parentJob 改為 SupervisorJob。

fun main() = runBlocking {
    val scope = CoroutineScope(SupervisorJob())
    scope.launch {
        1 / 0
        delay(100L)
        println("hello world 111")
    }
    scope.launch {
        delay(200L)
        println("hello world 222")
    }
    scope.launch {
        delay(300L)
        println("hello world 333")
    }
    delay(1000L)
    println("end")
}
/*
輸出信息:
Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.ArithmeticException: / by zero
hello world 222
hello world 333
end
*/

解決:使用 supervisorScope

supervisorScope 底層依然使用的是 SupervisorJob。

fun main() = runBlocking {
    supervisorScope {
        launch {
            1 / 0
            delay(100L)
            println("hello world 111")
        }
        launch {
            delay(200L)
            println("hello world 222")
        }
        launch {
            delay(300L)
            println("hello world 333")
        }
    }
    delay(1000L)
    println("end")
}
/*
輸出信息:
Exception in thread "main" java.lang.ArithmeticException: / by zero
hello world 222
hello world 333
end
*/

準(zhǔn)則六:使用 CoroutineExceptionHandler 處理復(fù)雜結(jié)構(gòu)的協(xié)程異常

問題:復(fù)雜結(jié)構(gòu)的協(xié)程異常

fun main() = runBlocking {
    val scope = CoroutineScope(coroutineContext)
    scope.launch {
        async { delay(100L) }
        launch {
            delay(100L)
            launch {
                delay(100L)
                1 / 0
            }
        }
        delay(100L)
    }
    delay(1000L)
    println("end")
}
/*
輸出信息:
Exception in thread "main" java.lang.ArithmeticException: / by zero
*/

原因:模擬一個復(fù)雜的協(xié)程嵌套場景,開發(fā)人員很難在每一個協(xié)程體中寫 try-catch,為了捕獲異常,可以使用 CoroutineExceptionHandler。

解決:使用CoroutineExceptionHandler

使用 CoroutineExceptionHandler 處理復(fù)雜結(jié)構(gòu)的協(xié)程異常,它只能在頂層協(xié)程中起作用。

fun main() = runBlocking {
    val myCoroutineExceptionHandler = CoroutineExceptionHandler { _, throwable ->
        println("捕獲異常:$throwable")
    }
    val scope = CoroutineScope(coroutineContext + Job() + myCoroutineExceptionHandler)
    scope.launch {
        async { delay(100L) }
        launch {
            delay(100L)
            launch {
                delay(100L)
                1 / 0
            }
        }
        delay(100L)
    }
    delay(1000L)
    println("end")
}
/*
輸出信息:
捕獲異常:java.lang.ArithmeticException: / by zero
end
*/

總結(jié)

  • 準(zhǔn)則一:協(xié)程的取消需要內(nèi)部的配合。
  • 準(zhǔn)則二:不要輕易打破協(xié)程的父子結(jié)構(gòu)。協(xié)程的優(yōu)勢在于結(jié)構(gòu)化并發(fā),他的許多特性都是建立在這之上的,如果打破了它的父子結(jié)構(gòu),會導(dǎo)致協(xié)程無法按照預(yù)期執(zhí)行。
  • 準(zhǔn)則三:捕獲 CancellationException 異常后,需要考慮是否重新拋出來。協(xié)程是依賴 CancellationException 異常來實現(xiàn)結(jié)構(gòu)化取消的,捕獲異常后需要考慮是否重新拋出來。
  • 準(zhǔn)則四:不要用 try-catch 直接包裹 launch、async。協(xié)程代碼的執(zhí)行順序與普通程序不一樣,直接使用 try-catch 可能不會達(dá)到預(yù)期效果。
  • 準(zhǔn)則五:使用 SupervisorJob 控制異常傳播范圍。SupervisorJob 是一種特殊的 Job,可以控制異常的傳播范圍,不會受到子協(xié)程中的異常而取消自己。
  • 準(zhǔn)則六:使用 CoroutineExceptionHandler 捕獲異常。當(dāng)協(xié)程嵌套層級比較深時,可以在頂層協(xié)程中定義 CoroutineExceptionHandler 捕獲整個作用域的所有異常。

到此這篇關(guān)于Kotlin 協(xié)程的異常處理的文章就介紹到這了,更多相關(guān)Kotlin 協(xié)程的異常處理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論