面试题答案
一键面试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
配合launch
或async
),这样异常会在结构化的作用域内传播,方便集中处理,避免未处理异常导致程序崩溃。 - 避免使用
GlobalScope
:GlobalScope
启动的协程不受结构化作用域控制,未处理异常易导致应用崩溃,应仅在必要且可保证异常处理的情况下使用。 - 使用
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
,对所有未处理异常进行集中处理,例如记录日志、向用户显示友好错误提示等。