Androdi kotlin Coroutines(协程)详解 (六)

Androdi kotlin Coroutines(协程)详解 (一)
Androdi kotlin Coroutines(协程)详解 (二)
Androdi kotlin Coroutines(协程)详解 (三)
Androdi kotlin Coroutines(协程)详解 (四)
Androdi kotlin Coroutines(协程)详解 (五)
Androdi kotlin Coroutines(协程)详解 (六)

6.1 异常的传播

协程构建器有两种形式:自动传播异常(launch 与 actor)或向用户暴露异常(async 与 produce)。 当这些构建器用于创建一个根协程时,即该协程不是另一个协程的子协程, 前者这类构建器将异常视为未捕获异常,类似 Java 的Thread.uncaughtExceptionHandler, 而后者则依赖用户来最终消费异常。

    private fun coroutineTryCatch() {
        GlobalScope.launch {
            try {
                val job = launch {
                    LogUtils.d("Throwing exception from launch")
                    throw IndexOutOfBoundsException() // 我们将在控制台打印 Thread.defaultUncaughtExceptionHandler
                }
                job.join()
                println("Joined failed job")
                val deferred = async { // async 根协程
                    LogUtils.d("Throwing exception from async")
                    throw ArithmeticException() // 没有打印任何东西,依赖用户去调用等待
                }
                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() // 我们将在控制台打印 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)

总结:

  • launch方式构建的协程,抛出异常视为未捕获异常,类似 Java 的 Thread.uncaughtExceptionHandler

6.3 async方式tryCatch

    private fun asyncTryCatch() {
        GlobalScope.launch {
            LogUtils.d("async start")
            val deferred = async { // async 根协程
                try {
                    LogUtils.d("Throwing exception from async")
                    throw ArithmeticException() // 没有打印任何东西,依赖用户去调用等待
                } catch (e: Exception) {
                    LogUtils.d("Caught 2 $e")
                }
            }
            deferred.await()
            LogUtils.d("Unreached")
        }
    }

总结:

  • 用户暴露异常(async 与 produce),依赖用户来最终消费异常

6.3 CoroutineExceptionHandler全局异常处理者

在 Android 中, uncaughtExceptionPreHandler 被设置在全局协程异常处理者中。

   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() // 我们将在控制台打印 Thread.defaultUncaughtExceptionHandler
            }
            job.join()
            LogUtils.d("Joined failed job")
            val deferred = async { // async 根协程
                LogUtils.d("Throwing exception from async")
                throw ArithmeticException() // 没有打印任何东西,依赖用户去调用等待
            }
            deferred.await()
            LogUtils.d("Unreached")
        }
    }

总结:

  • CoroutineExceptionHandler 的实现并不是用于子协程。
  • 如果根协程是launch启动,是可以捕获异常,而如果是async方式则捕获不了

6.4 异常聚合

当协程的多个子协程因异常而失败时, 一般规则是“取第一个异常”,因此将处理第一个异常。 在第一个异常之后发生的所有其他异常都作为被抑制的异常绑定至第一个异常。

private fun exceptionTogether() {
        GlobalScope.launch(handler) {
            LogUtils.d("exception together start")
            launch {
                try {
                    delay(500) // 当另一个同级的协程因 IOException 失败时,它将被取消
                    LogUtils.d("send exception")
                } finally {
                    throw ArithmeticException() // 第二个异常
                }
            }
            launch {
                delay(100)
                LogUtils.d("first exception")
                throw IOException() // 首个异常
            }
            LogUtils.d("exception together end")
        }
    }

6.5 监督

可能有以下需求

  • 一个 UI 的子作业执行失败了,它并不总是有必要取消(有效地杀死)整个 UI 组件, 但是如果 UI 组件被销毁了(并且它的作业也被取消了),由于它的结果不再被需要了,它有必要使所有的子作业执行失败
  • 服务进程孵化了一些子作业并且需要 监督 它们的执行,追踪它们的故障并在这些子作业执行失败的时候重启。
6.5.1 监督作业

它类似于常规的 Job,唯一的不同是:SupervisorJob 的取消只会向下传播。

private fun supervisorJob() {
        GlobalScope.launch(Dispatchers.Main) {
            val supervisor = SupervisorJob()
            with(CoroutineScope(coroutineContext + supervisor)) {
                // 启动第一个子作业——这个示例将会忽略它的异常(不要在实践中这么做!)
                val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
                    LogUtils.d("The first child is failing")
                    throw AssertionError("The first child is cancelled")
                }
                // 启动第二个子作业
                val secondChild = launch {
                    firstChild.join()
                    // 取消了第一个子作业且没有传播给第二个子作业
                    LogUtils.d("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
                    try {
                        delay(Long.MAX_VALUE)
                    } finally {
                        // 但是取消了监督的传播
                        LogUtils.d("The second child is cancelled because the supervisor was cancelled")
                    }
                }
                // 等待直到第一个子作业失败且执行完成
                firstChild.join()
                LogUtils.d("Cancelling the supervisor")
                supervisor.cancel()
                secondChild.join()
            }
        }
    }

6.5.2 监督作用域

对于作用域的并发,可以用 supervisorScope 来替代 coroutineScope 来实现相同的目的。它只会单向的传播并且当作业自身执行失败的时候将所有子作业全部取消。作业自身也会在所有的子作业执行结束前等待, 就像 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 来给我们的子作业一个机会来执行打印
                    yield()
                    LogUtils.d("Throwing an exception from the scope")
                    throw AssertionError()
                }
            } catch (e: AssertionError) {
                LogUtils.d("Caught an assertion error")
            }

        }

6.5.3 监督协程中的异常

监督协程中的每一个子作业应该通过异常处理机制处理自身的异常。 这种差异来自于子作业的执行失败不会传播给它的父作业的事实。 这意味着在 supervisorScope 内部直接启动的协程确实使用了设置在它们作用域内的CoroutineExceptionHandler,与父协程的方式相同

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

        }
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270