如何在Kotlin协程中使用异步缓存? [英] How do I use an async cache with Kotlin coroutines?

查看:299
本文介绍了如何在Kotlin协程中使用异步缓存?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个使用协程的Kotlin JVM服务器应用程序,我需要将缓存放在非阻塞网络调用的前面.我想我可以使用Caffeine

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都没有任何生命周期挂钩.缓存拥有ExecutorCompletableFuture实例,因此它已经以这种方式控制了加载任务的生命周期.我看不到将任务归于协程上下文会添加任何内容,而且我担心在停止使用缓存后无法正确关闭协程上下文.

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.

该方法通过使用

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协程中使用异步缓存?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆