取消 suspendCancellableCoroutine 内的回调

Oya*_*nlı 4 android callback cancellation kotlin-coroutines

我在 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)
                }
            }
        })
    }
}
Run Code Online (Sandbox Code Playgroud)

我使用片段的生命周期作用域协程范围调用此函数,并且我假设在片段被销毁时它被取消。然而,LeakCanary 报告说,由于这个监听器,我的片段正在泄漏,并且我通过日志验证了即使在协程被取消之后,回调仍然被调用。

所以看来用 suspendCancellableCoroutine 而不是 suspendCoroutine 包装不足以取消回调。我想我应该主动检查该作业是否处于活动状态,但是如何呢?我尝试coroutineContext.ensureActive()检查coroutineContext.isActive回调内部,但 IDE 给出错误,指出“只能在协程体内调用暂停函数”。我还能做些什么来确保作业被取消时它不会恢复?

Mar*_*nik 9

LeakCanary 报告说,由于这个监听器,我的片段正在泄漏,并且我通过日志验证了即使在协程被取消之后,回调仍然被调用。

是的,底层异步 API 不知道 Kotlin 协程,您必须使用它来显式传播取消。KotlininvokeOnCancellation专门为此提供了回调:

return suspendCancellableCoroutine { continuation ->
    this.setOnUtteranceProgressListener(object : JeLisUtteranceProgressListener() { 
        /* continuation.resume() */ 
    })
    continuation.invokeOnCancellation {
        this.setOnUtteranceProgressListener(null)
    }
}
Run Code Online (Sandbox Code Playgroud)


Paw*_*wel 6

如果您想删除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)
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @OyaCanlı问题是,由于您的延续处于挂起状态,它将通过(重新)抛出“CancellationException”立即完成并进入“finally”块。在这种情况下,您实际上并不需要显式取消侦听器。 (2认同)