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

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

Await Java 5 Futures in Kotlin Coroutines without blocking the Thread

问题

Sure, here's the translated code:

  1. // 从中取得一个`suspend`函数,我希望从中返回Java 5 `Future`的结果。Future对象来自另一个库[Firebase Cloud Firestore- Admin SDK for Java](https://firebase.google.com/docs/firestore/),并提供阻塞调用`get()`来检索所述future的结果。
  2. // 我的函数看起来像这样-
  3. suspend fun getPrefix(messageCreateEvent: MessageCreateEvent): String {
  4. val snapshot = db.collection("prefixes")
  5. .document(messageCreateEvent.guildId.get().asString())
  6. .get() //This returns a future
  7. .get() //Retrieves the future's result (Blocks thread; IDE gives warning)
  8. //Return the prefix
  9. return if (snapshot.exists())
  10. snapshot.getString("prefix") ?: DEFAULT_PREFIX
  11. else DEFAULT_PREFIX
  12. }
  13. ## 我考虑过的解决方案 ##
  14. 我首先考虑的是在`kotlinx.coroutine`中寻找扩展来连接这些futures虽然这些扩展是存在的但它们只适用于`CompletionStatge`所以我决定将future包装到其中一个中-
  15. ```kotlin
  16. val snapshot = CompleteableFuture.supplyAsync {
  17. db.collection("prefixes")
  18. .document(messageCreateEvent.guildId.get().asString())
  19. .get() // This returns a future
  20. .get() // Get the result
  21. }.await()

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

  1. val deferred = CompletableDeferred<DocumentSnapshot>()
  2. val future = db.collection("prefixes")
  3. .document(messageCreateEvent.guildId.get().asString())
  4. .get()
  5. future.addListener(
  6. Runnable { deferred.complete(future.get()) },
  7. ForkJoinPool.commonPool()
  8. )
  9. val snapshot = deferred.await()

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

  1. Please let me know if you need any further assistance!
  2. <details>
  3. <summary>英文:</summary>
  4. 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.
  5. My function looks like this-
  6. ```kotlin
  7. suspend fun getPrefix(messageCreateEvent: MessageCreateEvent): String {
  8. val snapshot = db.collection(&quot;prefixes&quot;)
  9. .document(messageCreateEvent.guildId.get().asString())
  10. .get() //This returns a future
  11. .get() //Retrieves the future&#39;s result (Blocks thread; IDE gives warning)
  12. //Return the prefix
  13. return if (snapshot.exists())
  14. snapshot.getString(&quot;prefix&quot;) ?: DEFAULT_PREFIX
  15. else DEFAULT_PREFIX
  16. }

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 ()-

  1. val snapshot = CompleteableFuture.supplyAsync {
  2. db.collection(&quot;prefixes&quot;)
  3. .document(messageCreateEvent.guildId.get().asString())
  4. .get() // This returns a future
  5. .get() // Get the result
  6. }.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-

  1. val deferred = CompletableDeferred&lt;DocumentSnapshot&gt;()
  2. val future = db.collection(&quot;prefixes&quot;)
  3. .document(messageCreateEvent.guildId.get().asString())
  4. .get()
  5. future.addListener(
  6. Runnable { deferred.complete(future.get()) },
  7. ForkJoinPool.commonPool()
  8. )
  9. 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.

  1. /**
  2. * Function to convert an ApiFuture into a coroutine return.
  3. */
  4. suspend fun <F : Any?, R : Any?> ApiFuture<F>.await(
  5. successHandler: (F) -> R,
  6. ): R {
  7. return suspendCoroutine { cont ->
  8. ApiFutures.addCallback(this, object : ApiFutureCallback<F> {
  9. override fun onFailure(t: Throwable?) {
  10. cont.resumeWithException(t ?: IOException("Unknown error"))
  11. }
  12. override fun onSuccess(result: F) {
  13. cont.resume(successHandler(result))
  14. }
  15. }, Dispatchers.IO.asExecutor())
  16. }
  17. }
  18. /**
  19. * inline function to retrieve a document as a POJO from a DocumentReference
  20. */
  21. suspend inline fun <reified T: Any> DocumentReference.toObject(): T? {
  22. return get().await<DocumentSnapshot, T?> {
  23. it.toObject(T::class.java)
  24. }
  25. }

Now you can write things like:

  1. suspend fun getUser(id: String): User? {
  2. return db.collection("users").document(id).toObject()
  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.

  1. /**
  2. * Function to convert an ApiFuture into a coroutine return.
  3. */
  4. suspend fun &lt;F : Any?, R : Any?&gt; ApiFuture&lt;F&gt;.await(
  5. successHandler: (F) -&gt; R,
  6. ): R {
  7. return suspendCoroutine { cont -&gt;
  8. ApiFutures.addCallback(this, object : ApiFutureCallback&lt;F&gt; {
  9. override fun onFailure(t: Throwable?) {
  10. cont.resumeWithException(t ?: IOException(&quot;Unknown error&quot;))
  11. }
  12. override fun onSuccess(result: F) {
  13. cont.resume(successHandler(result))
  14. }
  15. }, Dispatchers.IO.asExecutor())
  16. }
  17. }
  18. /**
  19. * inline function to retrieve a document as a POJO from a DocumentReference
  20. */
  21. suspend inline fun &lt;reified T: Any&gt;DocumentReference.toObject(): T? {
  22. return get().await&lt;DocumentSnapshot, T?&gt; {
  23. it.toObject(T::class.java)
  24. }
  25. }

Now you can write things like:

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

答案2

得分: 0

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

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

  1. 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.

  1. 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:

确定