Androdi kotlin Coroutines(協(xié)程)詳解 (一)
Androdi kotlin Coroutines(協(xié)程)詳解 (二)
Androdi kotlin Coroutines(協(xié)程)詳解 (三)
Androdi kotlin Coroutines(協(xié)程)詳解 (四)
Androdi kotlin Coroutines(協(xié)程)詳解 (五)
Androdi kotlin Coroutines(協(xié)程)詳解 (六)
6.1 異常的傳播
協(xié)程構(gòu)建器有兩種形式:自動(dòng)傳播異常(launch 與 actor)或向用戶暴露異常(async 與 produce)锐借。 當(dāng)這些構(gòu)建器用于創(chuàng)建一個(gè)根協(xié)程時(shí),即該協(xié)程不是另一個(gè)協(xié)程的子協(xié)程碉钠, 前者這類(lèi)構(gòu)建器將異常視為未捕獲異常隅很,類(lèi)似 Java 的Thread.uncaughtExceptionHandler表鳍, 而后者則依賴用戶來(lái)最終消費(fèi)異常芬沉。
private fun coroutineTryCatch() {
GlobalScope.launch {
try {
val job = launch {
LogUtils.d("Throwing exception from launch")
throw IndexOutOfBoundsException() // 我們將在控制臺(tái)打印 Thread.defaultUncaughtExceptionHandler
}
job.join()
println("Joined failed job")
val deferred = async { // async 根協(xié)程
LogUtils.d("Throwing exception from async")
throw ArithmeticException() // 沒(méi)有打印任何東西,依賴用戶去調(diào)用等待
}
deferred.await()
LogUtils.d("Unreached")
} catch (e: Exception) {
LogUtils.d("Caught $e")
}
}
6.2 launch方式tryCatch
private fun launchTryCatch() {
private fun launchTryCatch() {
GlobalScope.launch(Dispatchers.IO) {
try {
val job = launch {
LogUtils.d("Throwing exception from launch")
throw IndexOutOfBoundsException() // 我們將在控制臺(tái)打印 Thread.defaultUncaughtExceptionHandler
}
job.join()
LogUtils.i("Joined failed job")
} catch (e: Exception) {
LogUtils.e("catch exception $e")
}
}
}
}
E/ExceptionFragment: catch exception kotlinx.coroutines.JobCancellationException: StandaloneCoroutine is cancelling; job=StandaloneCoroutine{Cancelling}@a036e25
--------- beginning of crash
E/AndroidRuntime: FATAL EXCEPTION: DefaultDispatcher-worker-1
Process: com.huang.coroutine, PID: 4966
java.lang.IndexOutOfBoundsException
at com.huang.coroutine.ui.ExceptionFragment$launchTryCatch$1$job$1.invokeSuspend(ExceptionFragment.kt:68)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:56)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:571)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:738)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:678)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:665)
總結(jié):
- launch方式構(gòu)建的協(xié)程唤崭,拋出異常視為未捕獲異常棕孙,類(lèi)似 Java 的 Thread.uncaughtExceptionHandler
6.3 async方式tryCatch
private fun asyncTryCatch() {
GlobalScope.launch {
LogUtils.d("async start")
val deferred = async { // async 根協(xié)程
try {
LogUtils.d("Throwing exception from async")
throw ArithmeticException() // 沒(méi)有打印任何東西舔亭,依賴用戶去調(diào)用等待
} catch (e: Exception) {
LogUtils.d("Caught 2 $e")
}
}
deferred.await()
LogUtils.d("Unreached")
}
}
總結(jié):
- 用戶暴露異常(async 與 produce),依賴用戶來(lái)最終消費(fèi)異常
6.3 CoroutineExceptionHandler全局異常處理者
在 Android 中蟀俊, uncaughtExceptionPreHandler 被設(shè)置在全局協(xié)程異常處理者中分歇。
private val handler = CoroutineExceptionHandler { _, exception ->
LogUtils.e("CoroutineExceptionHandler got $exception")
}
private fun exceptionHandler() {
GlobalScope.launch(handler) {
val job = launch {
LogUtils.d("Throwing exception from launch")
throw IndexOutOfBoundsException() // 我們將在控制臺(tái)打印 Thread.defaultUncaughtExceptionHandler
}
job.join()
LogUtils.d("Joined failed job")
val deferred = async { // async 根協(xié)程
LogUtils.d("Throwing exception from async")
throw ArithmeticException() // 沒(méi)有打印任何東西,依賴用戶去調(diào)用等待
}
deferred.await()
LogUtils.d("Unreached")
}
}
總結(jié):
- CoroutineExceptionHandler 的實(shí)現(xiàn)并不是用于子協(xié)程欧漱。
- 如果根協(xié)程是launch啟動(dòng)职抡,是可以捕獲異常,而如果是async方式則捕獲不了
6.4 異常聚合
當(dāng)協(xié)程的多個(gè)子協(xié)程因異常而失敗時(shí)误甚, 一般規(guī)則是“取第一個(gè)異掣克Γ”谱净,因此將處理第一個(gè)異常。 在第一個(gè)異常之后發(fā)生的所有其他異常都作為被抑制的異常綁定至第一個(gè)異常擅威。
private fun exceptionTogether() {
GlobalScope.launch(handler) {
LogUtils.d("exception together start")
launch {
try {
delay(500) // 當(dāng)另一個(gè)同級(jí)的協(xié)程因 IOException 失敗時(shí)壕探,它將被取消
LogUtils.d("send exception")
} finally {
throw ArithmeticException() // 第二個(gè)異常
}
}
launch {
delay(100)
LogUtils.d("first exception")
throw IOException() // 首個(gè)異常
}
LogUtils.d("exception together end")
}
}
6.5 監(jiān)督
可能有以下需求
- 一個(gè) UI 的子作業(yè)執(zhí)行失敗了,它并不總是有必要取消(有效地殺死)整個(gè) UI 組件郊丛, 但是如果 UI 組件被銷(xiāo)毀了(并且它的作業(yè)也被取消了)李请,由于它的結(jié)果不再被需要了,它有必要使所有的子作業(yè)執(zhí)行失敗
- 服務(wù)進(jìn)程孵化了一些子作業(yè)并且需要 監(jiān)督 它們的執(zhí)行厉熟,追蹤它們的故障并在這些子作業(yè)執(zhí)行失敗的時(shí)候重啟导盅。
6.5.1 監(jiān)督作業(yè)
它類(lèi)似于常規(guī)的 Job,唯一的不同是:SupervisorJob 的取消只會(huì)向下傳播揍瑟。
private fun supervisorJob() {
GlobalScope.launch(Dispatchers.Main) {
val supervisor = SupervisorJob()
with(CoroutineScope(coroutineContext + supervisor)) {
// 啟動(dòng)第一個(gè)子作業(yè)——這個(gè)示例將會(huì)忽略它的異常(不要在實(shí)踐中這么做0追)
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
LogUtils.d("The first child is failing")
throw AssertionError("The first child is cancelled")
}
// 啟動(dòng)第二個(gè)子作業(yè)
val secondChild = launch {
firstChild.join()
// 取消了第一個(gè)子作業(yè)且沒(méi)有傳播給第二個(gè)子作業(yè)
LogUtils.d("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
try {
delay(Long.MAX_VALUE)
} finally {
// 但是取消了監(jiān)督的傳播
LogUtils.d("The second child is cancelled because the supervisor was cancelled")
}
}
// 等待直到第一個(gè)子作業(yè)失敗且執(zhí)行完成
firstChild.join()
LogUtils.d("Cancelling the supervisor")
supervisor.cancel()
secondChild.join()
}
}
}
6.5.2 監(jiān)督作用域
對(duì)于作用域的并發(fā),可以用 supervisorScope 來(lái)替代 coroutineScope 來(lái)實(shí)現(xiàn)相同的目的绢片。它只會(huì)單向的傳播并且當(dāng)作業(yè)自身執(zhí)行失敗的時(shí)候?qū)⑺凶幼鳂I(yè)全部取消滤馍。作業(yè)自身也會(huì)在所有的子作業(yè)執(zhí)行結(jié)束前等待, 就像 coroutineScope 所做的那樣底循。
private fun supervisorScope() {
GlobalScope.launch {
try {
supervisorScope {
val child = launch {
try {
LogUtils.d("The child is sleeping")
delay(Long.MAX_VALUE)
} finally {
LogUtils.d("The child is cancelled")
}
}
// 使用 yield 來(lái)給我們的子作業(yè)一個(gè)機(jī)會(huì)來(lái)執(zhí)行打印
yield()
LogUtils.d("Throwing an exception from the scope")
throw AssertionError()
}
} catch (e: AssertionError) {
LogUtils.d("Caught an assertion error")
}
}
6.5.3 監(jiān)督協(xié)程中的異常
監(jiān)督協(xié)程中的每一個(gè)子作業(yè)應(yīng)該通過(guò)異常處理機(jī)制處理自身的異常巢株。 這種差異來(lái)自于子作業(yè)的執(zhí)行失敗不會(huì)傳播給它的父作業(yè)的事實(shí)。 這意味著在 supervisorScope 內(nèi)部直接啟動(dòng)的協(xié)程確實(shí)使用了設(shè)置在它們作用域內(nèi)的CoroutineExceptionHandler熙涤,與父協(xié)程的方式相同
private fun supervisorException() {
GlobalScope.launch {
val handler = CoroutineExceptionHandler { _, exception ->
LogUtils.d("CoroutineExceptionHandler got $exception")
}
supervisorScope {
val child = launch(handler) {
LogUtils.d("The child throws an exception")
throw AssertionError()
}
LogUtils.d("The scope is completing")
}
LogUtils.d("The scope is completed")
}
}