共享惰性协程/记忆协程执行
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()
时开始,但是有协程。
到目前为止我尝试过的:
- 不能使用
by lazy{}
,因为 coroutineScope
在这里很重要,我不能使用 withContext(Dispatchers.IO)
,因为我使用自定义上下文(多线程,Spring 请求范围数据等)- 在这里传递我的上下文似乎很尴尬(这会是不好的做法吗?)
- 我无法在构造
MyClass
时传递 async(start = CoroutineStart.LAZY)
,因为如果 Deferred<T>
从未被 await()
开启,它会无限期地阻塞,这可能发生在两者都没有的情况下a()
或 b()
被调用。它还无限期地阻塞,因为在构造 MyClass
时构造了相应的 coroutineScope
,这将阻塞,因为 a()
和 b()
在 MyClass
完全构造之后稍后被调用因为(据我了解)a coroutineScope
只有在其所有子项都完成后才会解除阻塞,这对于在当前范围外等待的惰性异步不成立
- 当从不等待惰性异步时,使用更宽的协程上下文可能会泄漏 - 这是真的吗?我找不到太多相关信息
这是在 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()
}
上下文:
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()
时开始,但是有协程。
到目前为止我尝试过的:
- 不能使用
by lazy{}
,因为coroutineScope
在这里很重要,我不能使用withContext(Dispatchers.IO)
,因为我使用自定义上下文(多线程,Spring 请求范围数据等)- 在这里传递我的上下文似乎很尴尬(这会是不好的做法吗?) - 我无法在构造
MyClass
时传递async(start = CoroutineStart.LAZY)
,因为如果Deferred<T>
从未被await()
开启,它会无限期地阻塞,这可能发生在两者都没有的情况下a()
或b()
被调用。它还无限期地阻塞,因为在构造MyClass
时构造了相应的coroutineScope
,这将阻塞,因为a()
和b()
在MyClass
完全构造之后稍后被调用因为(据我了解)acoroutineScope
只有在其所有子项都完成后才会解除阻塞,这对于在当前范围外等待的惰性异步不成立 - 当从不等待惰性异步时,使用更宽的协程上下文可能会泄漏 - 这是真的吗?我找不到太多相关信息
这是在 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()
}