在Kotlin协程中等待Java 5的Futures而不阻塞线程。

huangapple go评论69阅读模式
英文:

Await Java 5 Futures in Kotlin Coroutines without blocking the Thread

问题

Sure, here's the translated code:

// 从中取得一个`suspend`函数,我希望从中返回Java 5 `Future`的结果。Future对象来自另一个库[Firebase Cloud Firestore- Admin SDK for Java](https://firebase.google.com/docs/firestore/),并提供阻塞调用`get()`来检索所述future的结果。

// 我的函数看起来像这样-
suspend fun getPrefix(messageCreateEvent: MessageCreateEvent): String {

    val snapshot = db.collection("prefixes")
        .document(messageCreateEvent.guildId.get().asString())
        .get() //This returns a future
        .get() //Retrieves the future's result (Blocks thread; IDE gives warning)

    //Return the prefix
    return if (snapshot.exists())
        snapshot.getString("prefix") ?: DEFAULT_PREFIX
    else DEFAULT_PREFIX
}

## 我考虑过的解决方案 ##
我首先考虑的是在`kotlinx.coroutine`中寻找扩展来连接这些futures虽然这些扩展是存在的但它们只适用于`CompletionStatge`所以我决定将future包装到其中一个中-

```kotlin
val snapshot = CompleteableFuture.supplyAsync {
    db.collection("prefixes")
        .document(messageCreateEvent.guildId.get().asString())
        .get() // This returns a future
        .get() // Get the result
}.await()

我经验有限,不确定这是否是正确的解决方案。我在一个编程社区中提出了我的问题,其中一个人建议我使用Deferred-

val deferred = CompletableDeferred<DocumentSnapshot>()
val future = db.collection("prefixes")
    .document(messageCreateEvent.guildId.get().asString())
    .get()

future.addListener(
    Runnable { deferred.complete(future.get()) },
    ForkJoinPool.commonPool()
)
            
val snapshot = deferred.await()

我已经花了很多时间搜索将futures与协程连接的方法,甚至在SO上都没有类似的问题。尽管如此,如果这个问题被标记为重复,我也不会感到惊讶。


Please let me know if you need any further assistance!

<details>
<summary>英文:</summary>

I have a `suspend` function from which I want to return the result of a Java 5 `Future`. The future object comes from another library [Firebase Cloud Firestore- Admin SDK for Java](https://firebase.google.com/docs/firestore/) and provides a blocking call `get()` to retrieve the result of the said future.

My function looks like this-

```kotlin
suspend fun getPrefix(messageCreateEvent: MessageCreateEvent): String {

    val snapshot = db.collection(&quot;prefixes&quot;)
        .document(messageCreateEvent.guildId.get().asString())
        .get() //This returns a future
        .get() //Retrieves the future&#39;s result (Blocks thread; IDE gives warning)

    //Return the prefix
    return if (snapshot.exists())
        snapshot.getString(&quot;prefix&quot;) ?: DEFAULT_PREFIX
    else DEFAULT_PREFIX
}

Solutions I have considered

The first thing that I considered was to look in kotlinx.coroutine for extensions to bridge the futures. While the extensions exist, they do only for CompletionStatge. So I decided to wrap the future into one ()-

val snapshot = CompleteableFuture.supplyAsync {
    db.collection(&quot;prefixes&quot;)
        .document(messageCreateEvent.guildId.get().asString())
        .get() // This returns a future
        .get() // Get the result
}.await()

I am quite inexperienced and not sure if this is was proper solution. I queried my question on a programming community, where a person recommended me to use a Deferred-

val deferred = CompletableDeferred&lt;DocumentSnapshot&gt;()
val future = db.collection(&quot;prefixes&quot;)
    .document(messageCreateEvent.guildId.get().asString())
    .get()

future.addListener(
    Runnable { deferred.complete(future.get()) },
    ForkJoinPool.commonPool()
)
            
val snapshot = deferred.await()

I've give it quite a time to search for a way to bridge futures to co-routines, there isn't even a similar question on SO. Through, I wouldn't be surprised if this question gets a duplicate mark.

答案1

得分: 3

The key to this problem is the suspendCoroutine function. The other non-obvious bit is that to add a callback to the ApiFuture, you use a static method on ApiFutures.

Here's an extension function that implements await() on an ApiFuture.

/**
 * Function to convert an ApiFuture into a coroutine return.
 */
suspend fun <F : Any?, R : Any?> ApiFuture<F>.await(
    successHandler: (F) -> R,
): R {
    return suspendCoroutine { cont ->
        ApiFutures.addCallback(this, object : ApiFutureCallback<F> {
            override fun onFailure(t: Throwable?) {
                cont.resumeWithException(t ?: IOException("Unknown error"))
            }

            override fun onSuccess(result: F) {
                cont.resume(successHandler(result))
            }
        }, Dispatchers.IO.asExecutor())
    }
}

/**
 * inline function to retrieve a document as a POJO from a DocumentReference
 */
suspend inline fun <reified T: Any> DocumentReference.toObject(): T? {
    return get().await<DocumentSnapshot, T?> {
        it.toObject(T::class.java)
    }
}

Now you can write things like:

suspend fun getUser(id: String): User? {
    return db.collection("users").document(id).toObject()
}
英文:

The key to this problem is the suspendCoroutine function. The other non-obvious bit is that to add a callback to the ApiFuture you use a static method on ApiFutures.

Here's an extension function that implements await() on an ApiFuture.

        /**
         * Function to convert an ApiFuture into a coroutine return.
         */
        suspend fun &lt;F : Any?, R : Any?&gt; ApiFuture&lt;F&gt;.await(
            successHandler: (F) -&gt; R,
        ): R {
            return suspendCoroutine { cont -&gt;
                ApiFutures.addCallback(this, object : ApiFutureCallback&lt;F&gt; {
                    override fun onFailure(t: Throwable?) {
                        cont.resumeWithException(t ?: IOException(&quot;Unknown error&quot;))
                    }

                    override fun onSuccess(result: F) {
                        cont.resume(successHandler(result))
                    }
                }, Dispatchers.IO.asExecutor())
            }
        }

/**
 * inline function to retrieve a document as a POJO from a DocumentReference
*/

suspend inline fun &lt;reified T: Any&gt;DocumentReference.toObject(): T? {
            return get().await&lt;DocumentSnapshot, T?&gt; {
                it.toObject(T::class.java)
            }
        }

Now you can write things like:

    suspend fun getUser(id: String): User? {
        return db.collection(&quot;users&quot;).document(id).toObject()

    }

答案2

得分: 0

你可以使用 kotlinx-coroutines-guava 来实现这个功能。

首先,使用 ApiFutureToListenableFutureApiFuture 转换为 ListenableFuture,然后使用 .await 来等待可监听的未来的完成,而无需阻塞线程。

ApiFutureToListenableFuture(this).await()
英文:

You can use kotlinx-coroutines-guava to do this

First convert ApiFuture to ListenableFuture using ApiFutureToListenableFuture then use .await to await completion of the listenableFuture without blocking a thread.

ApiFutureToListenableFuture(this).await()

huangapple
  • 本文由 发表于 2020年8月12日 16:12:21
  • 转载请务必保留本文链接:https://go.coder-hub.com/63372461.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定