为何CoroutineExceptionHandler未捕获异常?为何仅打印JobCancellationException栈迹?
关于CoroutineExceptionHandler的常见问题解答
1. 为何CoroutineExceptionHandler未能捕获/处理我的异常?
这大概率是你没摸透它的生效规则——CoroutineExceptionHandler只负责处理“逃逸到根协程”的未捕获异常,可不是所有协程里的异常它都能兜底。给你理几个核心场景:
- 如果你的协程是某个父协程的子协程(比如在
launch里嵌套另一个launch),而且父协程用的是普通Job(不是SupervisorJob),那子协程抛出的异常会直接甩给父协程,父协程会立刻取消自己和所有子协程,这时候子协程上下文里的CoroutineExceptionHandler根本没机会触发,只有最顶层的根协程的Handler才会处理最终传递过来的异常。 - 要是你用
async启动协程,它的异常会被封装在返回的Deferred对象里,除非你调用await(),否则异常不会主动抛出来,自然也不会被Handler捕获。 - 还有个细节:如果异常是
CancellationException或者它的子类,CoroutineExceptionHandler默认不会处理,因为这是协程取消的正常信号,不算“未处理的异常”。
2. 你的代码里SocketException去哪了?Handler为啥只打印JobCancellationException?
先把你的代码贴出来方便分析:
suspend fun foo() { val job = coroutineContext[Job]!! val socket = Socket() job.invokeOnCompletion(onCancelling = true) { if (!socket.isClosed) { socket.close() } } // 不可路由地址 -> 超时 // 在上方调用socket.close()后将抛出SocketException socket.connect(InetSocketAddress(...)) }
咱们一步步捋执行逻辑:
- 你给当前协程的
Job加了个invokeOnCompletion回调,onCancelling = true意味着这个回调会在协程刚进入取消流程时就执行,不是等协程完全结束。 - 当
socket.connect连接不可路由地址超时的时候,这个操作会触发协程取消(或者外部主动取消了这个Job),此时invokeOnCompletion回调执行,关闭了socket。 - 这时候
socket.connect还在执行,socket被强制关闭后就会抛出SocketException——但问题来了:当协程已经处于取消过程中时,新抛出的非CancellationException类型的异常会被协程框架“吞噬”,当作取消过程的附属异常,不会传递到CoroutineExceptionHandler。
只有触发协程取消的那个根异常(这里就是JobCancellationException)会被Handler处理,而SocketException会被标记为“被抑制的异常”(suppressed exception),你可以通过JobCancellationException的suppressed集合找到它,但默认打印堆栈的时候不会显示出来。
说白了就是:协程已经在取消的路上了,这时候再抛的异常都不算“需要处理的未捕获异常”,框架直接给你压下去了,只把触发取消的那个核心异常交给Handler处理。
内容的提问来源于stack exchange,提问作者kptlronyttcna
相关产品推荐
相关产品推荐

