Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

anyOf equivalent of Kotlin Deferred

Coroutine async returns Deferred<T> and there are example of lazy execution and usages of await.

However how can we wait for any one of the Deffered instances to complete?

In a nutshell

  // whats the equivalent of CompletableFuture.anyOf(...)?
  // is this how we do it? if so how costly is this?
  select<Unit> {
     deffered1.onAwait {}
     deffered2.onAwait {}
  }
like image 647
vach Avatar asked Mar 29 '18 10:03

vach


People also ask

What is Deferred in Kotlin?

Deferred is some kind of analog of Future in Java: in encapsulates an operation that will be finished at some point in future after it's initialization. But is also related to coroutines in Kotlin. From documentation: Deferred value is a non-blocking cancellable future — it is a Job that has a result.

How do you await in Kotlin?

In order to migrate to the async/await pattern, you have to return the async() result from your code, and call await() on the Deferred , from within another coroutine. By doing so, you can remove callbacks you used to use, to consume asynchronously provided values.

What are coroutines Kotlin?

A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. Coroutines were added to Kotlin in version 1.3 and are based on established concepts from other languages.


3 Answers

For variety, mentioning .merge(): Flow<T> alternative to select(), which I used to address a similar problem (get the first in a collection of deferreds to complete with a non-null value), i.e.

val firstCompletedResult = deferreds.map { it::await.asFlow() }.merge().first()

or if you like,

suspend fun <T> Iterable<Deferred<T>>.awaitAny(): T = map { it::await.asFlow() }.merge().first()
like image 191
nottheoilrig Avatar answered Oct 17 '22 09:10

nottheoilrig


Use select expression like this

        val deferred1: Deferred<String?> = GlobalScope.async { getValue1() }
        val deferred2: Deferred<String?> = GlobalScope.async { getValue2() }
        val deferred3: Deferred<String?> = GlobalScope.async { getValue3() }

        val deferredList = listOf(deferred1, deferred2, deferred3)
        val firstCompletedResult = select<String?> {
            deferredList.forEach {
                it.onAwait {}
            }
        }
        
        Log.d(TAG, "firstCompleted: $firstCompletedResult")
like image 32
Sabeeh Avatar answered Oct 17 '22 10:10

Sabeeh


Probably not the safest way to do things, but something like this should work:

inline suspend fun <T> Iterable<Deferred<T>>.awaitAny(): T {
    var completed: T? = null
    forEachIndexed { index, deferred ->
        deferred.invokeOnCompletion {
            completed = deferred.getCompleted()
            forEachIndexed { index2, deferred2 ->
                if (index != index2) {
                    deferred2.cancel(it)
                }
            }
        }
    }
    forEach {
        try {
            it.await()
        } catch (ignored: JobCancellationException) {
            // ignore
        }
    }
    return completed!!
}

Proof: The following prints 1000

launch(CommonPool) {
        // 10 - 1 second(s)
        val deferredInts = List(10, {
            val delayMs = (10 - it) * 1000
            async(CommonPool) {
                delay(delayMs)
                delayMs
            }
        })
        val first = deferredInts.awaitAny()
        println(first)
    }
like image 2
Julian Os Avatar answered Oct 17 '22 08:10

Julian Os