问题描述
我有一个使用协程的Kotlin JVM服务器应用程序,我需要将缓存放在非阻塞网络调用的前面.我想我可以使用Caffeine AsyncLoadingCache
以获得我需要的非阻塞缓存行为. AsyncCacheLoader
接口我需要实现使用CompletableFuture
.同时,我要调用的加载缓存条目的方法是suspend
函数.
I have a Kotlin JVM server application using coroutines and I need to put a cache in front of a non-blocking network call. I figure I can use a Caffeine AsyncLoadingCache
to get the non-blocking cache behaviour I need. The AsyncCacheLoader
interface I would need to implement uses CompletableFuture
. Meanwhile, the method I want to call to load the cache entries is a suspend
function.
我可以这样弥合差距:
abstract class SuspendingCacheLoader<K, V>: AsyncCacheLoader<K, V> {
abstract suspend fun load(key: K): V
final override fun asyncLoad(key: K, executor: Executor): CompletableFuture<V> {
return GlobalScope.async(executor.asCoroutineDispatcher()) {
load(key)
}.asCompletableFuture()
}
}
这将在提供的Executor
(默认为ForkJoinPool
)上运行load
功能,从咖啡因的角度来看,这是正确的行为.
This will run the load
function on the provided Executor
(by default, the ForkJoinPool
), which from the point of view of Caffeine is the correct behaviour.
但是,我知道我应该尝试避免使用GlobalScope发射协程.
However, I know that I should try to avoid using GlobalScope to launch coroutines.
我考虑过让SuspendingCacheLoader
实现 CoroutineScope
并管理自己的协程上下文.但是CoroutineScope
旨在由具有托管生命周期的对象实现.缓存和AsyncCacheLoader
都没有任何生命周期挂钩.缓存拥有Executor
和CompletableFuture
实例,因此它已经以这种方式控制了加载任务的生命周期.我看不到将任务归于协程上下文会添加任何内容,而且我担心在停止使用缓存后无法正确关闭协程上下文.
I considered having my SuspendingCacheLoader
implement CoroutineScope
and manage its own coroutine context. But CoroutineScope
is intended to be implemented by objects with a managed lifecycle. Neither the cache nor the AsyncCacheLoader
has any lifecycle hooks. The cache owns the Executor
and the CompletableFuture
instances, so it already controls the lifecycle of the loading tasks that way. I can't see that having the tasks be owned by a coroutine context would add anything, and I'm worried that I wouldn't be able to correctly close the coroutine context after the cache stopped being used.
编写自己的异步缓存机制非常困难,因此,如果可以的话,我想与Caffeine实现集成.
Writing my own asynchronous caching mechanism would be prohibitively difficult, so I'd like to integrate with the Caffeine implementation if I can.
是使用正确的方法实现AsyncCacheLoader
还是有更好的解决方案?
Is using GlobalScope
the right approach to implement AsyncCacheLoader
, or is there a better solution?
推荐答案
经过一番思考,我想出了一个更简单的解决方案,我认为它更惯用了协程.
After some thought I've come up with a much simpler solution that I think uses coroutines more idiomatically.
该方法通过使用 AsyncCache.get(key, mappingFunction)
,而不是实现AsyncCacheLoader
.但是,它会遵循此处其他一些答案的建议而忽略缓存配置为使用的Executor
.
The approach works by using AsyncCache.get(key, mappingFunction)
, instead of implementing an AsyncCacheLoader
. However, it ignores the Executor
that the cache is configured to use, following the advice of some of the other answers here.
class SuspendingCache<K, V>(private val asyncCache: AsyncCache<K, V>) {
suspend fun get(key: K): V = coroutineScope {
getAsync(key).await()
}
private fun CoroutineScope.getAsync(key: K) = asyncCache.get(key) { k, _ ->
future {
loadValue(k)
}
}
private suspend fun loadValue(key: K): V = TODO("Load the value")
}
请注意,这取决于kotlinx-coroutines-jdk8
的协程生成器和await()
函数.
Note that this depends on kotlinx-coroutines-jdk8
for the future
coroutine builder and the await()
function.
我认为忽略Executor
可能是正确的选择.正如@Kiskae指出的那样,默认情况下,缓存将使用ForkJoinPool
.选择使用它而不是默认的协程调度程序可能没有用.但是,如果需要,可以通过更改getAsync
函数来方便地使用它:
I think ignoring the Executor
is probably the right choice. As @Kiskae points out, the cache will use the ForkJoinPool
by default. Choosing to use that rather than the default coroutine dispatcher is probably not useful. However, it would be easy to use it if we wanted to, by changing the getAsync
function:
private fun CoroutineScope.getAsync(key: K) = asyncCache.get(key) { k, executor ->
future(executor.asCoroutineDispatcher()) {
loadValue(k)
}
}
这篇关于如何在Kotlin协程中使用异步缓存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!