【问题标题】:Kotlin coroutine concurrency and cached valueKotlin 协程并发和缓存值
【发布时间】:2019-09-19 09:08:10
【问题描述】:

我目前正在尝试围绕 Web 请求创建一个缓存层。到目前为止,我已经写了:

class Repository(private val webServices: WebServices) {
  private var cachedItems: List<Item>? = null

  suspend fun getItems(): List<Item> {
    cachedItems?.let { return it }

    val items = withContext(Dispatchers.IO) { webServices.getItems() }
    cachedItems = items
    return items
  }
}

我担心的是当两个调用者同时调用getItems() 时会发生什么。理想情况下,我只希望发生一个 Web 请求。使用协程时处理此问题的推荐方法是什么?

【问题讨论】:

  • 嗨!您只想使用协程来解决这个问题吗?
  • 是的!这只是学习如何使用它们的练习。

标签: kotlin kotlin-coroutines


【解决方案1】:

这是一个简单的解决方案。

class Repository(private val webServices: WebServices) {
  private val cachedItems = async(Dispatchers.IO, start = CoroutineStart.LAZY) {
    webServices.getItems()
  }

  suspend fun getItems(): List<Item> {
    return cachedItems.await()
  }
}

【讨论】:

  • 这看起来很整洁!它会避免在后续调用中跳转到不同的调度程序吗?
  • 会的。后续调用只是简单的读取,不会涉及调度程序。
  • 不错的一个。如果请求失败,它会传播异常吗?它会在后续调用中重新尝试 Web 请求吗?
  • 它将传播异常。结果也将缓存异常。所以每次调用该方法时,都会抛出相同的异常。如果您想重试该请求,您将需要一个不同于async 的构造。如果这些细节很重要,可能最好使用缓存库。
【解决方案2】:

首先,我认为您应该只使用缓存库,例如Caffeine。通常不建议重新发明轮子,尤其是对于具有很多移动部件的缓存。如果您想要 缓存,我也不推荐 lazy,因为 lazy 不支持缓存驱逐和类似的事情。

至于协程,您想要做的是一个单线程调度程序,您可以从 Java Executor 生成它,如下所示:Executors.newSingleThreadExecutor().asCoroutineDispatcher()。为此,您也可以使用actors 来获得相同的结果。

如果您想了解协程的一般工作原理,我会推荐 Kotlin 开发人员的 this article 以及 Roman Elizarov 的 great talk 关于这个主题!

【讨论】:

  • 我想我应该澄清一下,但我正在寻找一种方法来做到这一点,而无需任何外部依赖。 Executors.newSingleThreadExecutor() 绝对是一种方法,但我想知道是否有更优雅的方法。至于罗曼的谈话;是的,太棒了。我在观众席:)
  • 为什么需要更优雅的东西?只是为了优雅,还是为了一个目的?另外,为什么要在没有外部依赖的情况下解决这个问题?编写缓存解决方案是您核心业务的一部分吗?
【解决方案3】:

最简单的方法是使用lazy,它在后台同步,如下所示:

class Repository(private val webServices: WebServices) {
  private val cachedItems by lazy { webServices.getItems() }

  suspend fun getItems(): List<Item> {
    return withContext(Dispatchers.IO) { cachedItems }
  }
}

【讨论】:

    【解决方案4】:

    这是我的解决方案:

    class Repository(private val webServices: WebServices) {
        private val mutex = Mutex()
    
        private val items = mutableListOf<String>()
    
        suspend fun getItems(): List<String> {
            if (items.isNotEmpty()) {
                return items
            }
    
            if (!mutex.isLocked) {
                mutex.lock()
                val newItems = webServices.getItems()
                items.clear()
                items.addAll(newItems)
                mutex.unlock()
            } else {
                // not beauty code, it's still open question for me - how to improve it
                mutex.lock()
                mutex.unlock()
            }
            return items
        }
    }
    

    关于协程和并发的article 很有用。这是documentation Mutex

    【讨论】:

      【解决方案5】:

      我不知道这如何与协程交互。但在正常编码中:

      这始终是延迟获取的问题。我知道的两种主要方法是调用使用某种形式的同步,或者冒险多次获取作为避免阻塞的惩罚(如您的示例中所示)。

      前一种情况写起来比较复杂,但不一定要同步;您可以检查缓存的值,如果找到,请使用它;否则,您可以获得锁并再次检查,如果它仍然不存在,则仅获取该值。这种双重检查锁定很难正确处理,但是一旦获取、发布了值并且过时的缓存值过期,它就不需要再使用锁定了。

      但是,Kotlin 已经为您完成了,而不是您自己编写 - 您可以简单地将属性声明为委托by lazy,然后让 stdlib 完成所有艰苦的工作!它甚至可以让您指定要使用的线程安全模式。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2023-03-11
        • 1970-01-01
        • 2019-12-25
        • 2019-08-03
        • 2019-02-14
        • 2017-11-26
        • 1970-01-01
        • 2018-05-21
        相关资源
        最近更新 更多