Kotlin协程异常处理疑问:两种async调用为何结果不同?
Kotlin协程async异常处理差异解析
问题现象
使用以下抛出异常的挂起函数:
private suspend fun getMoreUsers(): ArrayList<User> { Timber.e("getMoreUsers task started : ${Thread.currentThread().name}") delay(1000) Timber.e("getMoreUsers task finished") throw IllegalStateException("exception thrown manually") val list = arrayListOf<User>() list.add(User("Deepu", 13)) return list }
不崩溃的代码
lifecycleScope.launch { try { val result = lifecycleScope.async { getMoreUsers() } val user = result.await() } catch (exception: Exception) { Timber.d("$exception handled !") } }
崩溃的代码
lifecycleScope.launch { try { val result = async { getMoreUsers() } val user = result.await() } catch (exception: Exception) { Timber.d("$exception handled !") } }
崩溃堆栈
java.lang.IllegalStateException: exception thrown manually handled ! 4246-4246 E FATAL EXCEPTION: main Process: com.vicky7230.learncoroutines, PID: 4246 java.lang.IllegalStateException: exception thrown manually at com.vicky7230.learncoroutines.MainActivity.getMoreUsers(MainActivity.kt:68) at com.vicky7230.learncoroutines.MainActivity.access$getMoreUsers(MainActivity.kt:12) at com.vicky7230.learncoroutines.MainActivity$getMoreUsers$1.invokeSuspend(Unknown Source:14) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTaskKt.resume(DispatchedTask.kt:235) at kotlinx.coroutines.DispatchedTaskKt.dispatch(DispatchedTask.kt:168) at kotlinx.coroutines.CancellableContinuationImpl.dispatchResume(CancellableContinuationImpl.kt:474) at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl(CancellableContinuationImpl.kt:508) at kotlinx.coroutines.CancellableContinuationImpl.resumeImpl$default(CancellableContinuationImpl.kt:497) at kotlinx.coroutines.CancellableContinuationImpl.resumeUndispatched(CancellableContinuationImpl.kt:595) at kotlinx.coroutines.android.HandlerContext$scheduleResumeAfterDelay$$inlined$Runnable$1.run(Runnable.kt:19) at android.os.Handler.handleCallback(Handler.java:942) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:201) at android.os.Looper.loop(Looper.java:288) at android.app.ActivityThread.main(ActivityThread.java:7872) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936) Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@e983b76, Dispatchers.Main.immediate]
原因解析
核心差异在于协程上下文的继承关系和async的异常传播规则:
1. 独立作用域async(不崩溃场景)
lifecycleScope.async是直接通过lifecycleScope这个独立作用域创建的协程,它与外层launch属于完全分离的协程任务:
- 这种async的异常会被封装在返回的
Deferred对象中,只有调用await()时才会抛出异常,因此外层的try-catch可以正常捕获处理,不会将异常传播给其他协程。 - 独立作用域的协程有自己的异常处理链路,不依赖外层launch的上下文。
2. 父协程上下文继承的async(崩溃场景)
直接调用async时,该协程会继承当前launch协程的上下文,成为launch的子协程:
- Kotlin协程默认规则:非独立作用域创建的子协程抛出异常时,会立即传播给父协程,而非等待
await()调用。 - 即使在
await()处添加了try-catch,异常在抛出时已经传递给父协程(launch),而lifecycleScope的默认异常处理逻辑会将未捕获的协程异常转为应用崩溃。
解决方案
如果要在launch内部使用async且避免崩溃,可以用supervisorScope包裹,它会改变子协程的异常传播规则:
lifecycleScope.launch { supervisorScope { try { val result = async { getMoreUsers() } val user = result.await() } catch (exception: Exception) { Timber.d("$exception handled !") } } }
supervisorScope下的子协程异常不会自动传播给父协程,必须通过await()主动获取并处理。
内容的提问来源于stack exchange,提问作者Vicky
相关产品推荐
相关产品推荐

