我有一个抽象的特性,其中有一些难以计算的要求,然后是这些计算结果的一些函数。我想保持这个特性简单,以便于理解和测试。
trait Calculator {
def hardToCalculate1: Int
def hardToCalculate2: Int
def hardToCalculate3: Int
def result1 = hardToCalculate1 + hardToCalculate2
def result2 = hardToCalculate2 + hardToCalculate3
def result3 = hardToCalculate1 + hardToCalculate3
}
当我实例化Calculator
时,我将使用Futures来计算hardToCalculate
个值。让我们说它们看起来像这样:
def f1 = future {
println("calculating 1")
1
}
def f2 = future {
println("calculating 2")
2
}
def f3 = future {
println("calculating 3")
3
}
所以,我可以像这样构建一个Future[Calculator]
:
val myCalc = for {
m1 <- f1
m2 <- f2
m3 <- f3
} yield new Calculator {
lazy val hardToCalculate1 = m1
lazy val hardToCalculate2 = m2
lazy val hardToCalculate3 = m3
}
然后,我可能会像这样使用myCalc
:
myCalc onComplete {
case Success(calc) => println("Result: " + calc.result1)
}
但是当我这样做时,我明白了:
calculating 1
calculating 2
calculating 3
Result: 3
如果我正在进行的计算实际需要它们,我只想执行那些期货。即使我使用hardToCalculate
声明lazy val
s,但在执行Future[Calculator].onComplete
时会计算所有三个。
这样做的一种方法是:
val calculator = new Calculator {
lazy val hardToCalculate1 = Await.result(f1, 10 seconds)
lazy val hardToCalculate2 = Await.result(f2, 10 seconds)
lazy val hardToCalculate3 = Await.result(f3, 10 seconds)
}
println("result: " + calculator.result1)
这产生了我想要的东西:
calculating 1
calculating 2
result: 3
但现在我已经完成了所有Await
阻止。我真正想要的是Future[Calculator]
,它将以懒惰的方式执行期货。如果不将Futures引入我的Calculator
特质,这可能吗?关于如何获得我在此之后的任何其他建议?
答案 0 :(得分:2)
如果您创建Future
(使用scala.concurrent.future
),无论您做什么,都会计算 。所以你需要一个完全不同的策略。
此外,您的界面甚至无法远程识别您将实际使用的数据。 myCalc
的计算如何知道onComplete
中您只使用result1
?
你可以:
仅使用lazy vals:
val calculator = new Calculator {
lazy val hardToCalculate1 = {
println("calculating 1")
1
}
// ...
}
亲:简单的 骗局:不同步
封装Future
以允许请求计算:
class ReqFuture[T](body: () => T) {
lazy val fut = future { body() }
}
但是现在你仍然遇到myCalc
将要求并等待所有这些问题的问题。因此,您必须在ReqFutures
中介绍Calculator
:
trait Calculator {
def hardToCalculate1: ReqFuture[Int]
// ...
def result1 = for {
h1 <- hardToCalculate1.fut
h2 <- hardToCalculate2.fut
} yield h1 + h2
}
专业:当你致电result1
时,只计算你需要的东西(但仍然只计算一次)
Con:result1
现在是Future[Int]
。因此Futures
完全渗透了您的Calculator
。
如果你不能影响Calculator
(我怀疑的话)并且无法改变result1,2,3
的代码,那么我很遗憾,你无法做任何事情来使执行变得懒惰和异步。
答案 1 :(得分:0)
我想尝试新的Async API,这是一个不错的测试。事实上,有一个示例与Async Github主页上的用例非常接近。
Async是SIP,可能会在某些时候成为标准Scala的一部分。
除了使用await
之外,这里的想法是你有一个在幕后使用异步逻辑的抽象add()
方法。这样它就会被Calculator
开发人员隐藏起来。
就个人而言,我也会添加API的异步版本,因此可以将Future
传递出Calculator
以与其他Future
组合。
trait Calculator {
def hardToCalculate1: Int
def hardToCalculate2: Int
def hardToCalculate3: Int
def add(a: => Int, b: => Int): Int
def result1 = add(hardToCalculate1, hardToCalculate2)
def result2 = add(hardToCalculate2, hardToCalculate3)
def result3 = add(hardToCalculate1, hardToCalculate3)
}
object So17677728 extends App with Calculator {
override def add(a: => Int, b: => Int): Int = {
val start = System.currentTimeMillis
val res = Await.result(asyncAdd(a, b), 2000 millis)
val end = System.currentTimeMillis
println(s"Total time: ${end - start} ms")
res
}
def asyncAdd(a: => Int, b: => Int): Future[Int] = {
async {
val fa = Future(a)
val fb = Future(b)
await(fa) + await(fb)
}
}
val random = Random
val maxSleep = 1000
def hardToCalculate1: Int = htc(1)
def hardToCalculate2: Int = htc(2)
def hardToCalculate3: Int = htc(3)
def htc(n: Int) = {
val sleepMs = random.nextInt(1000)
println(s"$n sleeping for $sleepMs ms")
Thread.sleep(sleepMs)
println(s"$n done sleeping")
n
}
println(s"result1: $result1\n")
println(s"result2: $result2\n")
println(s"result3: $result3\n")
}
输出
1 sleeping for 438 ms
2 sleeping for 244 ms
2 done sleeping
1 done sleeping
Total time: 497 ms
result1: 3
3 sleeping for 793 ms
2 sleeping for 842 ms
3 done sleeping
2 done sleeping
Total time: 844 ms
result2: 5
3 sleeping for 895 ms
1 sleeping for 212 ms
1 done sleeping
3 done sleeping
Total time: 896 ms
result3: 4
或者在add
Future
中,您可以async
进行理解,而不是await
/ {{1}}。