- 協(xié)程的3種作用域以及異常的傳播方式
- 協(xié)程異常的兩種捕獲方式及對(duì)比
- 協(xié)程異常的優(yōu)雅封裝
作用域以及異常的傳播方式
協(xié)程作用域分為頂級(jí)作用域,協(xié)同作用域與主從作用域,分別對(duì)應(yīng)GlobalScope,coroutineScope,supervisorScope。


說明:
C2-1發(fā)生異常的時(shí)候,C2-1->C2->C2-2->C2->C1->C3(包括里面的子協(xié)程)->C4
C3-1-1發(fā)生異常的時(shí)候,C3-1-1->C3-1-1-1,其他不受影響
C3-1-1-1發(fā)生異常的時(shí)候,C3-1-1-1->C3-1-1,其他不受影響
// C1和C2沒有關(guān)系
GlobalScope.launch { //協(xié)程C1
GlobalScope.launch {//協(xié)程C2
//...
}
}
// C2和C3是C1的子協(xié)程,C2和C3異常會(huì)取消C1
GlobalScope.launch { //協(xié)程C1
coroutineScoope {
launch{}//協(xié)程C2
launch{}//協(xié)程C3
}
}
// C2和C3是C1的子協(xié)程,C2和C3異常不會(huì)取消C1
GlobalScope.launch { //協(xié)程C1
supervisorScope {
launch{}//協(xié)程C2
launch{}//協(xié)程C3
}
}
如何捕獲異常
傳播的異常可以通過CoroutineExceptionHandler來捕獲
如果協(xié)程本身不使用try-catch子句自行處理異常,則不會(huì)重新拋出該異常,因此無法通過外部try-catch子句進(jìn)行處理。
異常會(huì)在“Job層次結(jié)構(gòu)中傳播”,可以由已設(shè)置的CoroutineExceptionHandler處理。如果未設(shè)置,則調(diào)用該線程的未捕獲異常處理程序。
CoroutineExceptionHandler
Try Catch與CoroutineExceptionHandler對(duì)比
協(xié)程支持兩種異常處理機(jī)制,那么我們應(yīng)該選擇哪種呢?
CoroutineExceptionHandler的官方文檔提供了一些很好的答案:
“ CoroutineExceptionHandler是用于全局“全部捕獲”行為的最后手段。您無法從CoroutineExceptionHandler中的異常中恢復(fù)。當(dāng)調(diào)用處理程序時(shí),協(xié)程已經(jīng)完成,并帶有相應(yīng)的異常。通常,處理程序用于記錄異常,顯示某種錯(cuò)誤消息,終止和/或重新啟動(dòng)應(yīng)用程序。
如果需要在代碼的特定部分處理異常,建議在協(xié)程內(nèi)部的相應(yīng)代碼周圍使用try / catch。這樣,您可以防止協(xié)程異常完成(現(xiàn)在已捕獲異常),重試該操作和/或采取其他任意操作:”
launch{} vs async{}
fun main() {
val topLevelScope = CoroutineScope(SupervisorJob())
topLevelScope.async {
throw RuntimeException("RuntimeException in async coroutine")
}
Thread.sleep(100)
}
// 沒有輸出
fun main() {
val coroutineExceptionHandler = CoroutineExceptionHandler { coroutineContext, exception ->
println("Handle $exception in CoroutineExceptionHandler")
}
val topLevelScope = CoroutineScope(SupervisorJob() + coroutineExceptionHandler)
topLevelScope.launch {
async {
throw RuntimeException("RuntimeException in async coroutine")
}
}
Thread.sleep(100)
}
// 輸出
// Handle java.lang.RuntimeException: RuntimeException in async coroutine in CoroutineExceptionHandler
launch和async協(xié)程中未捕獲的異常會(huì)立即在作業(yè)層次結(jié)構(gòu)中傳播。
但是,如果頂層Coroutine是從launch啟動(dòng)的,則異常將由CoroutineExceptionHandler處理或傳遞給線程的未捕獲異常處理程序。
如果頂級(jí)協(xié)程以async方式啟動(dòng),則異常封裝在Deferred返回類型中,并在調(diào)用.await()時(shí)重新拋出。
coroutineScope異常處理特性
范圍函數(shù)coroutineScope {}重新拋出其失敗的子協(xié)程的異常,而不是將其傳播到Job層次結(jié)構(gòu)中,這使我們能夠使用try-catch處理失敗的協(xié)程的異常。
supervisorScope異常處理特性
范圍函數(shù)supervisorScope {}在Job層次結(jié)構(gòu)中添加了一個(gè)新的獨(dú)立子范圍,并將SupervisorJob作為這個(gè)scope的'job'。
這個(gè)新作用域不會(huì)在“Job層次結(jié)構(gòu)”中傳播其異常,因此它必須自行處理其異常。直接從supervisorScope啟動(dòng)的協(xié)程是頂級(jí)協(xié)程。
頂級(jí)協(xié)程與子協(xié)程在使用launch()或async()啟動(dòng)時(shí)的行為有所不同,此外,還可以在它們中安裝CoroutineExceptionHandlers。
協(xié)程異常的優(yōu)雅封裝
我們可以對(duì)CoroutineExceptionHandler進(jìn)行封裝,利用kotlin擴(kuò)展函數(shù),實(shí)現(xiàn)類似RxJava的調(diào)用效果。最后調(diào)用效果如下:
fun fetch() {
viewModelScope.rxLaunch<String> {
onRequest = {
//網(wǎng)絡(luò)請(qǐng)求
resposity.getData()
}
onSuccess = {
//成功回調(diào)
}
onError = {
//失敗回調(diào)
}
}
}
主要利用kotlin擴(kuò)展函數(shù)及DSL語法,封裝協(xié)程異常處理,達(dá)到類似RxJava調(diào)用的效果
fun <T> CoroutineScope.rxLaunch(init: CoroutineBuilder<T>.() -> Unit) {
val result = CoroutineBuilder<T>().apply(init)
val coroutineExceptionHandler = CoroutineExceptionHandler { _, exception ->
result.onError?.invoke(exception)
}
launch(coroutineExceptionHandler) {
val res: T? = result.onRequest?.invoke()
res?.let {
result.onSuccess?.invoke(it)
}
}
}
class CoroutineBuilder<T> {
var onRequest: (suspend () -> T)? = null
var onSuccess: ((T) -> Unit)? = null
var onError: ((Throwable) -> Unit)? = null
}