我很难找到一个优雅的解决方案来链接一些未来。我试图实现的方法看起来像这样(它是缓存的一部分):
def acquire(key: A, producer: => Future[B]): Future[B]
算法大概是这样的:
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
完成,并返回对等体的结果,但是如果失败,在完成主要之前清理一个锁将来
答案 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
需要返回将来。
最后,我已将recover
和throw
替换为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
}
}
}
}