首页 > 其他分享 >Kotlin协程的异常处理

Kotlin协程的异常处理

时间:2023-10-10 20:00:14浏览次数:40  
标签:exception 协程 launch Kotlin child 异常 CoroutineExceptionHandler

捕获异常

Kotlin协程中执行的代码如果可能发生异常,最简单直接的办法也是可以通过 try-catch 语句来捕获异常

GlobalScope.launch {
    try {
        println(1 / 0)
    }  catch (e: Exception) { 
        //can catch exception
    }
}

但try-catch只能捕获该协程代码块中发生的异常,对于子协程中发生的异常则无能为力:

GlobalScope.launch {
    try {
        val child = launch {
            println(1 / 0)
        }
        child.join()
    }  catch (e: Exception) { 
        //can not catch exception
    }
}

Kotlin协程中的异常传递机制

当协程中的代码执行发生未捕获的异常时,会取消当前发生异常的协程及其子协程(如若当前协程有子协程)的执行,然后将异常传递给父协程并取消父协程。
父协程也是按照上述处理方式,取消自己以及子协程的执行,然后继续将向上传递异常并取消自己的父协程。

image

上述机制可以在 JobSupport 中找到对应的源码:

private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
    //省略....
    if (finalException != null) {
        val handled = cancelParent(finalException) || handleJobException(finalException)
            if (handled) (finalState as CompletedExceptionally).makeHandled()
        }
    }
    //省略....
}

子协程发生异常时会先调用 cancelParent 方法,将异常传递给父协程并尝试取消父协程。如果父协程不处理,则会由自己调用 handleJobException 方法来处理。

private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
    override fun handleJobException(exception: Throwable): Boolean {
        handleCoroutineException(context, exception)
        return true
    }
}

public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
    // Invoke an exception handler from the context if present
    try {
        context[CoroutineExceptionHandler]?.let {
            it.handleException(context, exception)
            return
        }
    } catch (t: Throwable) {
        handleCoroutineExceptionImpl(context, handlerException(exception, t))
        return
    }
    // If a handler is not present in the context or an exception was thrown, fallback to the global handler
    handleCoroutineExceptionImpl(context, exception)
}

handleJobException 方法中会找到当前Job中设置的 CoroutineExceptionHandler 来处理异常,找不到则会重新抛出异常最终导致Crash。

CoroutineExceptionHandlerCoroutineContext.Element的一个子类,只包含一个 handleException 方法:

public interface CoroutineExceptionHandler : CoroutineContext.Element {

    public fun handleException(context: CoroutineContext, exception: Throwable)

}

根据上述异常传递机制,异常最终会传递到根协程来处理,而根协程的parent job为null,因此异常由根协程调用 handleJobException 方法来处理异常,即由根协程中设置的 CoroutineExceptionHandler 来处理。

如下代码所示,我们在根协程中设置了 CoroutineExceptionHandler 并打印出子协程中发生的异常

val handler = CoroutineExceptionHandler { _, exception ->
    println("CoroutineExceptionHandler got $exception")
}
GlobalScope.launch(handler) {
    launch { // the first child
        delay(10)
        println(1 / 0)
    }
    launch { // the second child
        println("Second child start")
        delay(100)
        println("Second child end")
    }
}

上述代码输出如下:

Second child start
CoroutineExceptionHandler got java.lang.ArithmeticException: / by zero

可以看出第一个子协程中发生的异常在根协程中设置的 CoroutineExceptionHandler 中打印出来了,并且由于第一个子协程发生了异常,第二个子协程也被取消(最后的print语句没有执行到)。

特殊的是,对于使用 async 方法发起的协程如果作为根协程,其异常是在调用 await 方法时才会抛出。

val handler = CoroutineExceptionHandler { _, exception ->
    println("CoroutineExceptionHandler got $exception")
}
val deferred = GlobalScope.async(handler) {
    println("Throwing exception from async")
    throw ArithmeticException()
}
deferred.await()

此外,对于async发起的协程设置 CoroutineExceptionHandler 是不生效的,原因是async发起的协程其对应的Job实现并不是StandaloneCoroutine,而是DeferredCoroutine, DeferredCoroutine中的 handleJobException 方法仍然是 JobSupport 中的默认实现固定返回false,因此 async 发起的协程中发生的异常只能交给父协程来处理,而上述示例中async 发起的协程是根协程没有父协程,因此异常一定会被抛出而导致crash。
而如果async发起的协程不作为根协程,则其抛出的异常仍然能正常被根协程所处理,如下代码所示:

val handler = CoroutineExceptionHandler { _, exception ->
    println("Root got $exception")
}
GlobalScope.launch(handler) {
    val job = launch {
        delay(3000)
    }
    val deferred = async {
        println("Throwing exception from async")
        throw ArithmeticException()
    }
    job.join()
}

上述代码输出如下结果,可见async抛出的异常被根协程中设置的CoroutineExceptionHandler所捕获。

Throwing exception from async
Root got java.lang.ArithmeticException

SupervisorJob 与 supervisorScope

SupervisorJob 是一个函数,返回一个 SupervisorJobImpl 实例:

public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
    override fun childCancelled(cause: Throwable): Boolean = false
}

SupervisorJobImpl 继承自 JobImpl 类,覆写了 childCancelled 方法的实现固定返回false,其子协程调用 cancelParent 方法取消它时不会生效,因而其子协程发生的异常最终调用 handleJobException 方法处理,即Supervisor Job隔绝了子协程发生的异常继续向上传递的路径。

image

val supervisor = SupervisorJob()
val handler = CoroutineExceptionHandler { _, exception ->
    println("CoroutineExceptionHandler got $exception")
}
GlobalScope.launch(handler) {
    // launch the first child
    val firstChild = launch(supervisor) {
        println("The first child is failing")
        throw AssertionError("The first child is cancelled")
    }
    // launch the second child
    val secondChild = launch {
        delay(2000)
       	println("The second child completed")
    }
    joinAll(firstChild, secondChild)
}

上述代码输出如下结果:

The first child is failing
CoroutineExceptionHandler got java.lang.AssertionError: The first child is cancelled
The second child completed

由于 firstChild 的parent job是 SupervisorJob,其发生异常后调用 cancelParent 方法不会取消父协程,也就不会导致其兄弟协程被取消。

需要注意的是上述示例中的异常最终并不是交给根协程处理的,而是交给发生异常的子协程自己处理的,因为子协程实际上继承了根协程的 CorourtineExceptionHandler 。

supervisorScope 是一个函数,使用 supervisorScope 函数发起的协程其父协程是 SupervisorCoroutine,SupervisorCoroutine 的 Job 即是Supervisor Job。前述示例代码也可以使用supervisorScope来改造:

val handler = CoroutineExceptionHandler { _, exception ->
    println("CoroutineExceptionHandler got $exception")
}
supervisorScope {
    // launch the first child
    val firstChild = launch(handler) {
        println("The first child is failing")
        throw AssertionError("The first child is cancelled")
    }
    // launch the second child
    val secondChild = launch {
        delay(2000)
       	println("The second child completed")
    }
    joinAll(firstChild, secondChild)
}

标签:exception,协程,launch,Kotlin,child,异常,CoroutineExceptionHandler
From: https://www.cnblogs.com/jqctop1/p/17736325.html

相关文章

  • 异常捕获 计算耗时
    fromseleniumimportwebdriverfromselenium.webdriver.common.byimportByfromselenium.webdriver.common.keysimportKeysfromtimeimportsleep,ctimefromdatetimeimportdatetime#获得当前的时间datetime.now()可以计算出耗时defwait(loc):try:......
  • 关于Winform中使用DataGridView显示数据时,CheckBox选中状态异常问题
    使用C#创建的Winfrom项目,使用DataGridView显示数据,第一列中使用了DataGridViewCheckBoxColumn实现复选功能。但是当我绑定好数据后测试时,发现勾选和取消勾选的操作表现完全不按照预想的来——连续勾选几行后取消其中部分行的选中状态,再重复选中和取消时,原本不该被选中的checkBox......
  • 安防监控视频汇聚平台EasyCVR视频广场搜索异常,报错“通道未开启”的问题排查与解决
    安防视频监控系统EasyCVR视频汇聚平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等,能对外分发RTSP、RTMP、FLV、HLS、WebRTC等格式的视频流。有用户反馈,在EasyCVR平......
  • 【Https异常】This request has been blocked; the content must be served over HTTP
    参考:https://www.cnblogs.com/756623607-zhang/p/11790283.html一、问题出现场景项目从http升级到https后,jsp页面存在发送http请求的情况下就会出现该异常。因为HTTPS是HTTPoverSecureSocketLayer,以安全为目标的HTTP通道,所以在HTTPS承载的页面上不允许出现http请求......
  • MAC Office 插件异常 Run-time error '53'
    问题描述:Office版本和Mathtype版本不兼容,卸载mathtype后,加载项无法加载出现如下报错:Filenotfound:/Library/ApplicationSupport/Microsoft/Office365/UserContent.localized/Startup.localized/PowerPoint/MathType.bundle/Contents/MacOS/MTMacOLEshim.解决方法问题处在......
  • 使用 StringWriter 和 printWriter 打印异常原因
    StringWriter和PrintWriter都是Java中用于输出文本的类,它们的主要区别在于输出的目标不同。StringWriter是一个字符流,它将输出的内容保存在一个字符串缓冲区中,可以通过toString()方法获取缓冲区中的字符串。因此,StringWriter通常用于将输出内容保存到字符串中,而不是直接输出到控......
  • Astronomaly:利用 CNN 和主动学习识别 400 万张星系图像中的异常
    星系中的异常现象是我们了解宇宙的关键。然而,随着天文观测技术的发展,天文数据正以指数级别增长,超出了天文工作者的分析能力。尽管志愿者可以在线上参与对天文数据的处理,但他们只能进行一些简单的分类,还可能会遗漏一些关键数据。为此,研究者基于卷积神经网络和无监督学习开发了As......
  • rdlc报表打印预览时异常 An error occurred during local report processing. The def
    1.rdlc报表打印预览时会出现如下异常:2.解决办法:安装sqlsysclrtypesfor2012.msi并且重启电脑;......
  • 虚拟环境搭建、luffy后台项目创建,目录调整、封装logger、封装全局异常、封装Response
    虚拟环境搭建#1虚拟环境作用多个项目,自己有自己的环境,装的模块属于自己的#2使用pycharm创建-一般放在项目路径下:venv文件夹-lib文件夹---》site-package--》虚拟环境装的模块,都会放在这里-scripts--》python,pip命令#3本地创建......
  • 无涯教程-Kotlin - 简介
    Kotlin是一种在Java虚拟机上运行的静态类型编程语言,它也可以被编译成为JavaScript源代码。它主要是由俄罗斯圣彼得堡的JetBrains开发团队所发展出来的编程语言,其名称来自于圣彼得堡附近的科特林岛。2012年1月,著名期刊《Dr.Dobb'sJournal》中Kotlin被认定为该月的最佳语言。虽然......