Kotlin 多个暂停可取消协程

问题描述 投票:0回答:3

我的协程有问题。我使用一个协程,我想在第一个协程中执行第二个协程。当我尝试运行第二个协程时遇到一个错误:“只能在协程体内调用暂停函数”。我到处都有协程,那么问题出在哪里呢?这是代码:

 suspend fun firstCoroutine(): List<Decks> {

    val activeLanguage = userService.getActiveLanguage() ?: return emptyList()

    return suspendCancellableCoroutine { continuation ->

        db.collection("Decks")
            .whereArrayContains("languages", activeLanguage)
            .get()
            .addOnSuccessListener { documents ->

                val items = ArrayList<Decks>()

                if (documents != null) {
                    for (document in documents) {
                        val id: String = document.id

                        var knowCountForDeck: Int = secondCoroutine(id). <-- here is problem
                        
                        val name: String = document.data["name"] as String
                        val languages: List<String> =
                            document.data["languages"] as List<String>
                        items.add(Decks(id, name, languages))
                    }

                }
                continuation.resume(items)
            }
            .addOnFailureListener { err ->
                continuation.resumeWithException(err)
            }
    }
}

suspend fun secondCoroutine(collectionId: String): Int {

    val userId = userService.getCurrentUserId() ?: return 0

    return suspendCancellableCoroutine { continuation ->

        db.collection("Users").document(userId).collection(collectionId).get()
            .addOnSuccessListener { cards ->

                var knowCountForDeck = 0

                if (!cards.isEmpty) {
                    for (card in cards) {
                        if (card.data["status"] == "know") {
                            knowCountForDeck += 1
                        }
                    }
                }
                continuation.resume(knowCountForDeck)
            }
            .addOnFailureListener { err ->
                continuation.resumeWithException(err)
            }
    }
}
kotlin coroutine
3个回答
2
投票

您正在尝试从回调中调用协程,该回调不是您的协程的一部分,因此它无法调用挂起函数。

协程的主要优点之一是您可以避免使用回调并按顺序编写代码。

许多 API 已经包含使用回调的挂起函数替代方案。如果我正确地猜测您正在使用 Firebase,您可以使用挂起函数

await()
而不是使用侦听器。那么你不需要使用
suspendCoroutine
suspendCancellableCoroutine
将回调转换为挂起函数:

suspend fun firstCoroutine(): List<Decks> {

    val activeLanguage = userService.getActiveLanguage() ?: return emptyList()

    val documents = db.collection("Decks")
        .whereArrayContains("languages", activeLanguage)
        .get()
        .await()
    val items = documents.map { document ->
        val id: String = document.id

        var knowCountForDeck: Int = someSuspendFunction(id)

        val name: String = document.data["name"] as String
        val languages: List<String> =
            document.data["languages"] as List<String>
        Decks(id, name, languages)
    }
    return items
}

如果您使用的 API 没有可用的挂起功能,那么要使用

suspendCoroutine
编写自己的 API,我建议编写一个可以与任何任务一起使用的简单版本,然后在您的特定应用程序中使用它代码。它看起来像这样:

suspend fun <T> Task<T>.await() = suspendCoroutine<T> { continuation ->
    addOnSuccessListener {
        continuation.resume(it)
    }
    addOnFailureListener {
        continuation.resumeWithException(it)
    }
}

或者为了更好地遵循 Kotlin 约定,您可以返回 null,而不是在可恢复的故障上抛出异常:

suspend fun <T: Any> Task<T>.awaitResultOrNull(): T? = suspendCoroutine<T> { continuation ->
    addOnSuccessListener {
        continuation.resume(it)
    }
    addOnFailureListener {
        continuation.resume(null)
    }
}

0
投票

这可以简单地通过将调用移动到调用协程内的

secondCoroutine
函数来解决

yourScope.launch{
   val decks = firstCoroutine()  
   decks.forEach{
       val countForDeck = secondCoroutine(it.id)
       // Do something with countForDeck
   }
}

0
投票

避免嵌套

suspendCancellableCoroutine
调用 使用 suspendCancellableCoroutine 时,不应在另一个也使用 suspendCancellableCoroutine 的函数中调用内部使用 suspendCancellableCoroutine 的函数。这可能会导致协程生命周期出现意外行为或复杂性。

不正确的方法

suspend fun A(): T = suspendCancellableCoroutine { continuation -> 
    B() // B() internally uses suspendCancellableCoroutine
}

这里,A() 不必要地将 B() 包装在 suspendCancellableCoroutine 中。如果 B() 已经将基于回调的 API 与协程桥接,则这种嵌套是多余的,应该避免。

正确做法

suspendCancellableCoroutine 旨在将基于回调的 API 与面向协程的代码桥接起来。如果 B() 已经处理了基于回调的逻辑,则 A() 不需要再次处理。相反,让 A() 仍然是一个简单的挂起函数。

示例:

suspend fun A() {
    coroutineScope {
        // Call B() directly within A's coroutine context
        B()
    }
}

如果 A() 碰巧返回某个值并且该返回值由 B() 处理,您可以简单地执行以下操作:

suspend fun A() {
        return coroutineScope {
            
            return@coroutineScope B() // here your returning B()
        }
    }
© www.soinside.com 2019 - 2024. All rights reserved.