调试协程
目录
调试协程
调试异步程序是很困难的, 因为通常会有多个并发协程同时在工作. 为了解决这个问题, kotlinx.coroutines 提供了额外的调试功能: 调试模式, 栈追踪(Stacktrace)恢复, 以及调试代理.
调试模式
kotlinx.coroutines 的第一个调试功能是调试模式. 这个功能可以设置系统属性 DEBUG_PROPERTY_NAME 来启用, 或者启用断言运行 Java (使用 -ea 标志) 来启用. 后一种方式能够在单元测试中默认启用调试模式.
调试模式会为每个启动的协程附加一个唯一的 名称. 协程名称可以在通常的 Java 调试器中看到, 也可以在协程的字符串表示中, 或者在执行命名协程的线程名称中看到. 这个功能的开销可以忽略不计, 可以安全地默认启用, 以简化日志记录和诊断.
栈追踪(Stacktrace)恢复
栈追踪(Stacktrace)恢复是调试模式的另一个有用功能. 这个功能在调试模式下默认启用, 但可以将系统属性 kotlinx.coroutines.stacktrace.recovery 设置为 false, 来单独禁用这个功能.
栈追踪恢复会通过复制的方式, 尝试将异步异常的栈追踪与接收者的栈追踪拼接在一起, 不仅提供异常抛出的位置信息, 还提供异常被异步重新抛出或捕获的位置信息.
可以运行同一个程序, 查看它在 main 函数中等待异步操作时的实际的栈追踪, 来演示这个功能 (可运行的代码在 这里):
没有栈追踪恢复 | 有栈追踪恢复 |
|---|---|
![]() | ![]() |
这种方式唯一的缺点, 是失去了异常的引用透明性.
栈追踪恢复的机制
本节解释栈追踪恢复的内部机制, 可以跳过.
当异常在协程之间重新抛出时 (例如, 穿过 withContext 或 Deferred.await 边界), 栈追踪恢复机制会尝试创建原始异常的副本 (以原始异常作为 cause), 然后使用协程相关的栈帧(Stack Frame), 重写副本的栈追踪 (使用 Throwable.setStackTrace), 然后抛出结果异常, 而不是原始异常.
异常复制逻辑很简单:
如果异常类实现了 CopyableThrowable, 则使用 CopyableThrowable.createCopy.
createCopy可以返回null, 这样可以选择性退出特定异常的恢复.如果异常类有特定于类的字段, 不是从 Throwable 继承得到, 则异常不复制.
否则, 通过反射调用异常的某个 public 构造函数, 并选择性的调用
initCause.如果反射创建的副本的消息发生了变化 (异常构造函数将修改后的
message参数传递给了父类), 则不复制异常, 以保留人类可读的消息. CopyableThrowable 没有这种限制, 允许副本的message与原始异常不同.
调试代理
kotlinx-coroutines-debug 模块提供了 kotlinx.coroutines 中最强大的调试功能之一.
这是一个带有 JVM 代理的单独模块, 这个 JVM 代理追踪所有活跃的协程, 对它们进行内省(introspect)和转储, 与线程转储(thread dump)命令类似, 并且还增强了栈追踪, 添加了协程创建位置的信息.
关于如何使用调试代理的完整教程, 请参见相应的 readme.
Android 优化
在使用 R8 版本 1.6.0 或更高版本的优化 (Release) 构建中, 调试模式 和 栈追踪恢复 都会被永久关闭. 详情请参见 Android 的"优化"章节.

