在 suspendCancellableCoroutine 中取消回调
Cancelling a callback inside a suspendCancellableCoroutine
我在 suspendCancellableCoroutine 中包装了一个回调,将其转换为挂起函数:
suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
val uniqueUtteranceId = getUniqueUtteranceId(text)
speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
Timber.d("word is read, resuming with the next word")
continuation.resume(true)
}
}
})
}
}
我正在使用片段的 lifecycleScope 协程范围调用此函数,我假设它在片段被销毁时被取消。然而,LeakCanary 报告说我的片段因为这个监听器而泄漏,我用日志验证了即使在协程被取消后回调也被调用了。
所以看起来用 suspendCancellableCoroutine 而不是 suspendCoroutine 包装不足以取消回调。我想我应该主动检查作业是否处于活动状态,但是如何检查?我尝试 coroutineContext.ensureActive()
并在回调中检查 coroutineContext.isActive
但 IDE 给出了一个错误,指出 "suspension functions can be called only within coroutine body" 什么如果作业被取消,我还能做些什么来确保它不会恢复?
LeakCanary reported that my fragment was leaking because of this listener and I verified with logs that the callback was called even after the coroutine is cancelled.
是的,底层异步 API 不知道 Kotlin 协程,您必须使用它来显式传播取消。 Kotlin 专门为此提供了 invokeOnCancellation
回调:
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
/* continuation.resume() */
})
continuation.invokeOnCancellation {
this.setOnUtteranceProgressListener(null)
}
}
除了已接受的答案外,我还认识到 continuation 对象有一个 isActive 属性 还有。因此,或者我们可以在恢复之前检查协程是否在回调中仍然处于活动状态:
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener()
{
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
if (continuation.isActive) {
continuation.resume(true)
}
}
}
})
continuation.invokeOnCancellation {
this.setOnUtteranceProgressListener(null)
}
}
如果您想删除 JeLisUtteranceProgressListener
而不管结果(成功、取消或其他错误),您可以改用经典 try/finally 块:
suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
val uniqueUtteranceId = getUniqueUtteranceId(text)
speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
return try {
suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
Timber.d("word is read, resuming with the next word")
continuation.resume(true)
}
}
})
} finally {
this.setOnUtteranceProgressListener(null)
}
}
我在 suspendCancellableCoroutine 中包装了一个回调,将其转换为挂起函数:
suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
val uniqueUtteranceId = getUniqueUtteranceId(text)
speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
Timber.d("word is read, resuming with the next word")
continuation.resume(true)
}
}
})
}
}
我正在使用片段的 lifecycleScope 协程范围调用此函数,我假设它在片段被销毁时被取消。然而,LeakCanary 报告说我的片段因为这个监听器而泄漏,我用日志验证了即使在协程被取消后回调也被调用了。
所以看起来用 suspendCancellableCoroutine 而不是 suspendCoroutine 包装不足以取消回调。我想我应该主动检查作业是否处于活动状态,但是如何检查?我尝试 coroutineContext.ensureActive()
并在回调中检查 coroutineContext.isActive
但 IDE 给出了一个错误,指出 "suspension functions can be called only within coroutine body" 什么如果作业被取消,我还能做些什么来确保它不会恢复?
LeakCanary reported that my fragment was leaking because of this listener and I verified with logs that the callback was called even after the coroutine is cancelled.
是的,底层异步 API 不知道 Kotlin 协程,您必须使用它来显式传播取消。 Kotlin 专门为此提供了 invokeOnCancellation
回调:
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
/* continuation.resume() */
})
continuation.invokeOnCancellation {
this.setOnUtteranceProgressListener(null)
}
}
除了已接受的答案外,我还认识到 continuation 对象有一个 isActive 属性 还有。因此,或者我们可以在恢复之前检查协程是否在回调中仍然处于活动状态:
return suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener()
{
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
if (continuation.isActive) {
continuation.resume(true)
}
}
}
})
continuation.invokeOnCancellation {
this.setOnUtteranceProgressListener(null)
}
}
如果您想删除 JeLisUtteranceProgressListener
而不管结果(成功、取消或其他错误),您可以改用经典 try/finally 块:
suspend fun TextToSpeech.speakAndWait(text: String) : Boolean {
val uniqueUtteranceId = getUniqueUtteranceId(text)
speak(text, TextToSpeech.QUEUE_FLUSH, null, uniqueUtteranceId)
return try {
suspendCancellableCoroutine { continuation ->
this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() {
override fun onDone(utteranceId: String?) {
if(utteranceId == uniqueUtteranceId) {
Timber.d("word is read, resuming with the next word")
continuation.resume(true)
}
}
})
} finally {
this.setOnUtteranceProgressListener(null)
}
}