我如何 "wrap" 在惯用的 Kotlin 中调用这个不完全 "by lazy" 结果缓存函数?
How would I "wrap" this not-quite-"by lazy" result caching function call in idiomatic Kotlin?
我不能使用 "by lazy",因为回调需要 suspendCoroutine
,如果阻塞主线程,它会在 android 中运行,所以我必须使用以下 [=22] =] 模式一遍又一遍。有没有办法将它包装在 funButUseCachedResultsIfTheyAlreadyExist
模式中以封装 xCached 对象?
private var cameraDeviceCached: CameraDevice? = null
private suspend fun cameraDevice(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
... deep callbacks with cont.resume(camera) ...
}.also {
cameraDeviceCached = it
}
}
当我真正想写的是
private suspend fun cameraDevice(): CameraDevice = theMagicFunction { cont ->
... deep callbacks with cont.resume(camera) ...
}
我会写这个作为答案,因为不可能 post 在评论中添加太多代码。
你要找的是这样的:
private suspend fun cameraDevice() = theMagicFunction {
CameraDevice()
}()
suspend fun theMagicFunction(block: ()->CameraDevice): () -> CameraDevice {
var cameraDeviceCached: CameraDevice? = null
return fun(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
cont.resume(block())
}.also {
cameraDeviceCached = it
}
}
}
不幸的是,这不会编译,因为闭包不能被挂起,本地函数也不是。
除非我错过了那里的解决方案,否则我最好的建议是将其封装在 class 中,如果这个变量让您感到困扰的话。
您可以通过包装 async
调用来构建通用解决方案,如下所示:
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.LAZY
class LazySuspendFun<out T>(
scope: CoroutineScope,
private val block: suspend () -> T
) {
private val deferred = scope.async(Dispatchers.Unconfined, LAZY) { block() }
suspend operator fun invoke() = deferred.await()
}
fun <T> CoroutineScope.lazySuspendFun(block: suspend () -> T) =
LazySuspendFun(this, block)
这是一个说明如何使用它的简单示例。请注意,我们能够组合它们,以便我们使用延迟启动的值作为获取另一个值的依赖项:
val fetchToken = lazySuspendFun<String> {
suspendCoroutine { continuation ->
Thread {
info { "Fetching token" }
sleep(3000)
info { "Got token" }
continuation.resume("hodda_")
}.start()
}
}
val fetchPosts = lazySuspendFun<List<String>> {
val token = fetchToken()
suspendCoroutine { continuation ->
Thread {
info { "Fetching posts" }
sleep(3000)
info { "Got posts" }
continuation.resume(listOf("${token}post1", "${token}post2"))
}
}
}
在调用方,您必须在某个协程上下文中,以便可以调用挂起函数:
myScope.launch {
val posts = fetchPosts()
...
}
此解决方案足够稳健,您可以同时请求多次该值,而初始化程序只会 运行 一次。
我不能使用 "by lazy",因为回调需要 suspendCoroutine
,如果阻塞主线程,它会在 android 中运行,所以我必须使用以下 [=22] =] 模式一遍又一遍。有没有办法将它包装在 funButUseCachedResultsIfTheyAlreadyExist
模式中以封装 xCached 对象?
private var cameraDeviceCached: CameraDevice? = null
private suspend fun cameraDevice(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
... deep callbacks with cont.resume(camera) ...
}.also {
cameraDeviceCached = it
}
}
当我真正想写的是
private suspend fun cameraDevice(): CameraDevice = theMagicFunction { cont ->
... deep callbacks with cont.resume(camera) ...
}
我会写这个作为答案,因为不可能 post 在评论中添加太多代码。
你要找的是这样的:
private suspend fun cameraDevice() = theMagicFunction {
CameraDevice()
}()
suspend fun theMagicFunction(block: ()->CameraDevice): () -> CameraDevice {
var cameraDeviceCached: CameraDevice? = null
return fun(): CameraDevice {
cameraDeviceCached?.also { return it }
return suspendCoroutine { cont: Continuation<CameraDevice> ->
cont.resume(block())
}.also {
cameraDeviceCached = it
}
}
}
不幸的是,这不会编译,因为闭包不能被挂起,本地函数也不是。
除非我错过了那里的解决方案,否则我最好的建议是将其封装在 class 中,如果这个变量让您感到困扰的话。
您可以通过包装 async
调用来构建通用解决方案,如下所示:
import kotlinx.coroutines.*
import kotlinx.coroutines.CoroutineStart.LAZY
class LazySuspendFun<out T>(
scope: CoroutineScope,
private val block: suspend () -> T
) {
private val deferred = scope.async(Dispatchers.Unconfined, LAZY) { block() }
suspend operator fun invoke() = deferred.await()
}
fun <T> CoroutineScope.lazySuspendFun(block: suspend () -> T) =
LazySuspendFun(this, block)
这是一个说明如何使用它的简单示例。请注意,我们能够组合它们,以便我们使用延迟启动的值作为获取另一个值的依赖项:
val fetchToken = lazySuspendFun<String> {
suspendCoroutine { continuation ->
Thread {
info { "Fetching token" }
sleep(3000)
info { "Got token" }
continuation.resume("hodda_")
}.start()
}
}
val fetchPosts = lazySuspendFun<List<String>> {
val token = fetchToken()
suspendCoroutine { continuation ->
Thread {
info { "Fetching posts" }
sleep(3000)
info { "Got posts" }
continuation.resume(listOf("${token}post1", "${token}post2"))
}
}
}
在调用方,您必须在某个协程上下文中,以便可以调用挂起函数:
myScope.launch {
val posts = fetchPosts()
...
}
此解决方案足够稳健,您可以同时请求多次该值,而初始化程序只会 运行 一次。