共享惰性协程/记忆协程执行

Shared lazy coroutine / memoized coroutine execution

上下文:

import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope

data class DatabaseData(
    val a: String,
    val b: String
)

interface DatabaseFetcher {
    suspend fun get(): DatabaseData
}

class MyClass(
    private val databaseFetcher: DatabaseFetcher
) {
    suspend fun a() = coroutineScope {
        val a = async { databaseFetcher.get().a }
        //imagine more async{}'s here
        a //imagine a gets computed in regards to the other async{}'s as well
    }

    suspend fun b() = databaseFetcher.get().b
}

class MyController(private val databaseFetcher: DatabaseFetcher) {
    suspend fun someFun() = coroutineScope {
        // the reduced example doesn't need a coroutineScope of course, imagine some async{} here
        MyClass(databaseFetcher)
    }
}

如果在 MyClass 上调用 a()b(),我将尝试仅调用一次 databaseFetcher.get()。基本上是一个懒惰的未来,它在调用 a()b() 时开始,但是有协程。

到目前为止我尝试过的:

这是在 GraphQL 的上下文中完成的,其中可以选择 a b 或两者。对此有样板解决方案,但由于我仍在学习协程,我想知道是否有一个我还没有看到的优雅解决方案。 CoroutineStart.LAZY 问题真的让我大吃一惊:)

我找到了解决方案:

fun <T : () -> U, U> T.memoized(): suspend () -> Deferred<U> {
    val self = this
    val deferred: CompletableDeferred<U> = CompletableDeferred()
    val started = AtomicBoolean(false)
    return suspend {
        if (started.compareAndExchange(false, true)) {
            deferred
        } else {
            coroutineScope {
                async {
                    deferred.complete(self())
                    deferred.await()
                }
            }
        }
    }
}

任何 () -> T 函数(基本上任何带有捕获参数的函数)都可以是 .memoized()。无论被调用者首先调用返回的 suspend fun 将用于启动 Deferred<U>,同时允许所述被调用者在他认为合适的时候阻塞:

val expensive = { someExpensiveFun(param, param2 }.memoize();
withContext(Dispatchers.IO) { // or some other context
    val a = expensive()
    val b = expensive()

    a.await()
    b.await()
}