【问题标题】:How would I "wrap" this not-quite-"by lazy" result caching function call in idiomatic Kotlin?我将如何在惯用的 Kotlin 中“包装”这个不太“懒惰”的结果缓存函数调用?
【发布时间】:2023-03-25 02:15:01
【问题描述】:

我不能使用“by lazy”,因为回调需要suspendCoroutine,如果它阻塞了主线程,它会在android中运行,所以我必须一遍又一遍地使用以下“缓存结果”模式。有没有办法将它包装在 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) ...
}

【问题讨论】:

    标签: kotlin kotlin-coroutines


    【解决方案1】:

    您可以通过包装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()
       ...
    }
    

    此解决方案足够健壮,您可以同时多次请求该值,而初始化程序将只运行一次。

    【讨论】:

    • 它启动一个线程来恢复继续。这只是为了独立的模拟代码,而不是你应该在真实代码中模拟的东西。
    【解决方案2】:

    我会写这个作为答案,因为不可能在 cmets 中发布太多代码。

    你正在寻找的是这样的:

    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
            }
        }
    }
    

    不幸的是,这不会编译,因为闭包不能被挂起,本地函数也不能。

    除非我错过了那里的解决方案,否则我最好的建议是将它封装在一个类中,如果这个变量太困扰你的话。

    【讨论】:

    • “是将其封装在一个类中”-同意,我想我必须这样做。
    猜你喜欢
    • 2019-11-22
    • 1970-01-01
    • 1970-01-01
    • 2018-09-25
    • 2011-08-30
    • 2018-07-20
    • 1970-01-01
    • 2021-08-25
    • 1970-01-01
    相关资源
    最近更新 更多