使用“回滚”代码平面映射未来

时间:2013-04-02 22:36:16

标签: scala asynchronous future

我很难找到一个优雅的解决方案来链接一些未来。我试图实现的方法看起来像这样(它是缓存的一部分):

def acquire(key: A, producer: => Future[B]): Future[B]

算法大概是这样的:

  • 如果密钥被锁定,则立即通过运行时异常(是否会提前使用Future.failed?)
  • 否则打开future { getOrRefresh }块,因为它需要一些时间来检索密钥
  • getOrRefresh要么返回一个直的B,那就是未来的结果和方法
  • 或者需要运行producer

最后一步意味着我需要从未来的 中“展平”未来。也就是说,我不能做outer.flatMap,所以我想策略是使用Await

现在,Await有一个奇怪的精神分裂症,你可以使用方法Option[Try[B]]获得ready,或使用B获得展开的result 。问题是,在完成外部未来之前,我需要在Failure的情况下释放锁定,所以我必须坚持Await.ready

这很难看:

val fut   = producer
val prod  = Await.ready(fut, Duration.Inf).value.get
if (prod.isFailure) sync.synchronized { locked = false }
prod.get

难道这可丑吗?必须有更好的方法来做到这一点。


重复一遍:从Future[B]运行中,一些同行的未来也用B完成,并返回对等体的结果,但是如果失败,在完成主要之前清理一个锁将来

2 个答案:

答案 0 :(得分:0)

尝试使用recover(With)从而避免使用Await。不过,它看起来很笨重,因为我需要重新抛出异常

import concurrent._

trait Cache[A, B] {
  class Entry(var locked: Boolean = true)

  private var map  = Map.empty[A, Entry]
  private val sync = new AnyRef

  implicit def exec: ExecutionContext

  def readEntry(key: A): Option[B]

  def acquire(key: A, producer: => Future[B]): Future[B] = sync.synchronized {
    map.get(key) match {
      case Some(e) =>
        if (e.locked) throw new IllegalStateException()
        e.locked     = true
        val existing = future { readEntry(key).get }
        val refresh  = existing.recoverWith {
          case _: NoSuchElementException => producer
        }
        refresh.recover {
          case t => sync.synchronized(e.locked = false); throw t
        }

      case _ => producer.map { value =>
        sync.synchronized {
          map += key -> new Entry()
        }
        value
      }
    }
  }
}

如果您有任何建议,请将其作为单独的答案发布。

答案 1 :(得分:0)

我对您的实施做了一些更改。

首先,我已将您的初始throw锁定为Future.failed,因为期货的消费者应该安全地认为唯一的失败是未来的失败。< / p>

接下来,我不是从get致电Option[B]上的readEntry,而是将其作为未来的结果返回。然后我flatMap结果,以便在producer的情况下替换None生成的未来(地图将导致未来[未来[B]])。对于Some,我会从值返回Future.successful,因为flatMap需要返回将来。

最后,我已将recoverthrow替换为andThen,因为我们希望失败的Future能够传播并且只想链接一方 - 解锁未来对未来的影响。

trait Cache[A, B] {
  class Entry(var locked: Boolean = true)

  private var map  = Map.empty[A, Entry]
  private val sync = new AnyRef

  def readEntry(key: A): Option[B] = ???

  def acquire(key: A, producer: => Future[B]): Future[B] = sync.synchronized {
    map.get(key) match {
      case Some(e) =>
        if (e.locked)
          Future.failed(new IllegalStateException())
        else {
        e.locked = true
        future { readEntry(key)}.flatMap {
          case None => producer.andThen {
            case Failure(_) => sync.synchronized(e.locked = false)
          }
          case Some(value) => Future.successful(value)
        }
      }

      case _ => producer.map { value =>
        sync.synchronized {
          map += key -> new Entry()
        }
        value
      }
    }
  }
}