MST

星途 面试题库

面试题:Kotlin异常处理与协程的结合及最佳实践

在Kotlin的协程编程中,异常处理与常规函数的异常处理有何不同?如何在协程中捕获和处理异常,以避免未处理的异常导致程序崩溃?请举例说明,并阐述在协程异常处理方面的最佳实践。
48.2万 热度难度
编程语言Kotlin

知识考点

AI 面试

面试题答案

一键面试

1. 异常处理与常规函数的不同

  • 常规函数:异常通常通过try - catch块直接捕获,异常会立即中断函数执行,并在最近的try - catch块中被捕获处理,如果没有合适的try - catch块,异常会向上层调用栈传播,最终可能导致程序崩溃。
  • 协程:协程中异常处理相对复杂。协程是轻量级线程,异常默认不会立即中断协程执行。如果协程在GlobalScope启动,未处理的异常可能会导致应用崩溃;但在其他有结构化的协程作用域(如CoroutineScope)内,异常处理有不同机制。而且,协程异常传播遵循结构化并发原则,异常会在协程层次结构中传播。

2. 在协程中捕获和处理异常的方法

  • 使用try - catch
import kotlinx.coroutines.*

fun main() = runBlocking {
    try {
        launch {
            throw RuntimeException("Test exception")
        }.join()
    } catch (e: RuntimeException) {
        println("Caught exception: $e")
    }
}

在此例中,在try - catch块内启动一个协程,若协程抛出异常,try - catch块可以捕获并处理。

  • 使用CoroutineExceptionHandler
import kotlinx.coroutines.*

val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught exception: $exception")
}

fun main() = runBlocking {
    GlobalScope.launch(handler) {
        throw RuntimeException("Test exception")
    }.join()
}

CoroutineExceptionHandler可用于处理协程中的未捕获异常,通过在启动协程时传入该handler,若协程抛出未捕获异常,会由handler处理。

3. 协程异常处理的最佳实践

  • 结构化并发:尽量使用结构化的协程作用域(如CoroutineScope配合launchasync),这样异常会在结构化的作用域内传播,方便集中处理,避免未处理异常导致程序崩溃。
  • 避免使用GlobalScopeGlobalScope启动的协程不受结构化作用域控制,未处理异常易导致应用崩溃,应仅在必要且可保证异常处理的情况下使用。
  • 使用supervisorScope:当需要启动一组相互独立的协程,且一个协程的异常不影响其他协程时,supervisorScope很有用。例如:
import kotlinx.coroutines.*

fun main() = runBlocking {
    supervisorScope {
        launch {
            delay(1000)
            throw RuntimeException("First coroutine exception")
        }
        launch {
            delay(2000)
            println("Second coroutine still running")
        }
    }
    println("SupervisorScope finished")
}

在此例中,第一个协程抛出异常,不会影响第二个协程执行。不过,supervisorScope本身不会捕获异常,异常仍会向上传播到外层作用域,需要外层合适处理。

  • 集中处理异常:在协程作用域的上层,设置统一的CoroutineExceptionHandler,对所有未处理异常进行集中处理,例如记录日志、向用户显示友好错误提示等。