任务#应用与任务#delay

时间:2017-03-11 19:54:34

标签: scala scalaz

考虑通过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#delayTask#apply

1 个答案:

答案 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,则无法进入另一个方向 - 隐式策略将确定计算的评估位置以及所有计算结果。