考虑通过scala.concurrent.Task
创建的以下Task#delay
实例:
val t =
Task.delay { println(Thread.currentThread); Thread.sleep(5000); 42 }
我写了一个将异步运行t
的方法。
def f = t.runAsync {
case \/-(x) => println(x)
case -\/(e) => println(e.getMessage)
}
运行它会显示f
完全评估,即等待5秒,然后再次评估。换句话说,第二个f
似乎要等到第一个f
完成
scala> {f; f; }
Thread[run-main-0,5,run-main-group-0]
42
Thread[run-main-0,5,run-main-group-0]
42
然后,我使用t
重写了Task#apply
:
val u =
Task { println(Thread.currentThread); Thread.sleep(5000); 42 }
同样,我定义了一个用u
执行runAsync
的方法:
def g = u.runAsync {
case \/-(x) => println(x)
case -\/(e) => println(e.getMessage)
}
最后,我跑了两个g
。
scala> {g; g}
Thread[pool-3-thread-2,5,run-main-group-0]
Thread[pool-3-thread-3,5,run-main-group-0]
scala> 42
42
但是,在上述结果中,g
或多或少同时运行。
我原本以为{f; f; }
会异步运行,即与g
相同。但是,在我看来,调用f
会导致阻塞。
修改
Task关于runAsync
的文档说明:
这个Future的头部的任何纯粹的非异步计算都将被强制在调用线程中。
由于t
的正文是非异步的,我认为上面的注释解释了为什么它被阻止,即强制在调用线程中。"
何时适合使用Task#delay
与Task#apply
?
答案 0 :(得分:4)
您可以将Task.delay
视为() => Try[A]
之类的奇特版本。它暂停了对计算的评估,但是没有任何关于评估最终将要运行的线程等等的任何说法(这意味着它将在当前线程上运行)。 / p>
这通常就是你想要的。考虑这样的定义:
val currentTime: Task[Long] = Task.xxx(System.currentTimeMillis)
我们无法使用now
,因为这会立即评估时间(定义时只评估一次)。我们可以使用apply
,但强制这个计算的异步边界是浪费和不必要的 - 我们实际上希望它在当前线程中运行,而不是现在。这正是delay
提供的内容。
通常,当您对计算进行建模时,如果某些内容的计算成本一直很高,您可能需要考虑Task.apply
,这意味着评估将始终发生在当前确定的线程上隐式ExecutorService
。这可能会使使用更加清洁,但会牺牲灵活性 - 您可以将关于计算评估的运行时特性的内容烘焙到其定义中。
使用delay
来定义异步计算的好处是你总是可以通过用Task
包裹你的Task.fork
来强制实现异步边界,这样你就可以获得基本相同的东西。如果你用Task.apply
定义了计算,那就有了。如果使用Task.apply
,则无法进入另一个方向 - 隐式策略将确定计算的评估位置以及所有计算结果。