suspendCoroutine
是一个低阶构建器,它允许我们挂起一个协程,并在未来的某个时间点通过手动恢复它来返回值。
suspendCoroutine
块时,当前协程会被立即挂起。Continuation
: 你会获得一个 Continuation<T>
类型的实例 cont
。这个 Continuation
对象是协程状态的快照,也是连接回调世界与协程世界的“桥梁”。Continuation
的恢复方法来结束挂起,并将结果传递回协程。
cont.resume(value: T)
: 当异步操作成功时调用,协程将恢复并返回 value
。cont.resumeWithException(exception: Throwable)
: 当异步操作失败时调用,协程将恢复并抛出 exception
。[重要规则] Continuation
的恢复方法必须且只能被调用一次。多次调用会导致 IllegalStateException
。
假设我们有一个老式的用户信息获取函数:
// 传统的回调式 API
fun fetchUserAsync(callback: (Result<String>) -> Unit) {
// 模拟网络延迟
Thread.sleep(1000)
// 模拟成功或失败
if (Math.random() > 0.5) {
callback(Result.success("User Data"))
} else {
callback(Result.failure(Exception("Network Error")))
}
}
我们可以使用 suspendCoroutine
将其包装成一个挂起函数:
import kotlin.coroutines.suspendCoroutine
import kotlin.coroutines.resume
suspend fun fetchUserSuspend(): String = suspendCoroutine { continuation ->
println("协程已挂起,正在执行异步任务...")
fetchUserAsync { result ->
// 在回调中根据结果恢复协程
result.onSuccess { data ->
println("任务成功,恢复协程并返回值")
continuation.resume(data)
}.onFailure { error ->
println("任务失败,恢复协程并抛出异常")
continuation.resumeWithException(error)
}
}
}
现在,我们可以像调用其他 suspend
函数一样调用它,彻底告别了回调嵌套。
suspendCoroutine
有一个致命的缺陷:它无法响应协程的取消。如果调用 fetchUserSuspend
的协程被取消了,底层的 fetchUserAsync
操作仍然会继续执行,造成资源浪费。
suspendCancellableCoroutine
解决了这个问题,它是绝大多数场景下的首选方案。
suspendCancellableCoroutine
除了提供 Continuation
外,还允许你注册一个 invokeOnCancellation
回调。
suspendCancellableCoroutine
的协程被外部取消时(例如,Job.cancel()
被调用),invokeOnCancellation
闭包会被立即触发。OkHttp 是一个典型的支持取消操作的回调式库,是演示 suspendCancellableCoroutine
的绝佳范例。
import okhttp3.*
import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlinx.coroutines.suspendCancellableCoroutine
// 使用扩展函数为 OkHttpClient 添加一个 suspend fun
suspend fun OkHttpClient.newCallSuspend(request: Request): Response {
// 使用 suspendCancellableCoroutine
return suspendCancellableCoroutine { continuation ->
// 1. 创建一个 Call 对象
val call = newCall(request)
// 2. 在协程取消时,我们也取消这个 Call
continuation.invokeOnCancellation {
println("协程被取消,正在取消 OkHttp Call...")
call.cancel()
}
// 3. 异步执行请求,并在回调中恢复协程
call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// 如果协程已经取消了,resume 会被忽略
continuation.resume(response)
}
override fun onFailure(call: Call, e: IOException) {
// 只有在协程 still active 的情况下才恢复异常
if (continuation.isActive) {
continuation.resumeWithException(e)
}
}
})
}
}
特性 | suspendCoroutine |
suspendCancellableCoroutine |
---|---|---|
基本功能 | 将回调 API 包装为挂起函数。 | 将回调 API 包装为挂起函数。 |
取消支持 | ❌ 不支持。协程取消无法传递给底层任务。 | ✅ 完全支持。通过 invokeOnCancellation 实现双向取消。 |
安全性 | 较低,可能导致资源泄漏和“僵尸”任务。 | 高,确保了结构化并发的完整性。 |
推荐度 | ★☆☆☆☆ (仅用于包装不可取消的回调或教学) | ★★★★★ (生产环境首选) |
最佳实践: 始终优先使用 suspendCancellableCoroutine
。只有当你确定你正在包装的底层异步 API 绝对不支持任何形式的取消操作时,才考虑退而求其次使用 suspendCoroutine
。