安全恢复:SafeContinuation
协程框架通过 SafeContinuation 保证了一个核心契约:每个 Continuation 的 resume 必须且只能调用一次。
为什么需要保护?
在极其复杂的异步场景下(如从第三方 Callback 转换,或涉及多线程竞争),可能会错误地调用两次 resume。
- 如果不加保护:会导致程序崩溃 (
IllegalStateException: Already resumed) 或逻辑错乱。 - 使用
SafeContinuation:它会原子性地检查状态,确保只有第一次resume生效,或者抛出更有意义的异常。
内部状态机
SafeContinuation 内部维护一个 result 字段,该字段充当状态机:
| 状态值 | 含义 |
|---|---|
| UNDECIDED | 初始状态,还没有任何结果。 |
| SUSPENDED | 协程已真正挂起(返回了 COROUTINE_SUSPENDED)。 |
| RESUMED | 协程已被恢复。 |
运作流程
场景 1:真正的异步挂起
- 调用
suspendCoroutine。 - 内部将状态置为
SUSPENDED。 - 函数返回
COROUTINE_SUSPENDED,线程释放。 - 异步回调触发,调用
resume()。 - CAS 操作发现状态是
SUSPENDED,将其更新为结果,并唤醒协程。
场景 2:同步直接返回 (未挂起)
- 调用
suspendCoroutine。 - 但在
block内部立即调用了resume()。 - CAS 操作发现状态是
UNDECIDED,直接将结果存入字段,不唤醒(因为还没挂起呢)。 suspendCoroutine检查到已有结果,直接返回该结果,不发生挂起。- 这就是为什么
suspendCoroutine也可以处理同步逻辑。
- 这就是为什么
开发者视角
在日常开发中,建议使用 suspendCancellableCoroutine,它内部默认使用了 SafeContinuation 机制,并且额外支持了被取消时的响应(invokeOnCancellation)。
kotlin
suspend fun <T> Call<T>.await(): T = suspendCancellableCoroutine { cont ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
// SafeContinuation 保证这里的 resume 是安全的
cont.resume(response.body()!!)
}
override fun onFailure(call: Call<T>, t: Throwable) {
cont.resumeWithException(t)
}
})
// 如果协程被取消,取消 HTTP 请求
cont.invokeOnCancellation { cancel() }
}