任务#apply 与任务#delay

Task#apply versus Task#delay

给定以下 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

然后,我用Task#apply重写了t

val u = 
  Task { println(Thread.currentThread); Thread.sleep(5000); 42 }

同样,我定义了一个执行 urunAsync 的方法:

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的文档注释:

Any pure, non-asynchronous computation at the head of this Future will be forced in the calling thread.

由于 t 的主体是非异步的,我想上面的评论解释了为什么它被阻止,即 "forced in the calling thread."

什么时候使用 Task#delayTask#apply 比较合适?

您可以将 Task.delay 视为 () => Try[A] 之类的奇特版本。它暂停了计算的评估,但没有任何关于评估最终将在 运行 上进行的线程等的说明(这意味着它只是在当前线程上进行 运行) .

这通常正是您想要的。考虑这样的定义:

val currentTime: Task[Long] = Task.xxx(System.currentTimeMillis)

我们不能使用 now,因为那样会立即计算时间(而且只有一次,根据定义)。我们可以使用 apply,但是为这个计算强制一个异步边界是浪费和不必要的——我们实际上 想要 它到 运行 在当前线程中,只是不正确现在。这正是 delay 提供的。

一般来说,当您对计算进行建模时,如果某些东西的计算量总是很大,您可能需要考虑 Task.apply,这意味着评估将始终在由当前线程确定的线程上进行隐式 ExecutorService。这可能会以牺牲灵活性为代价使使用更简洁一些——您正在将您知道的关于计算评估的运行时间特征的东西烘焙到它的定义中。

使用 delay 定义异步计算的好处在于,您始终可以通过用 Task.fork 包装 Task 来强制异步边界如果您使用 Task.apply 定义了计算,您将拥有的东西。不可能朝另一个方向前进 — 如果您使用 Task.apply,隐式策略将确定在哪里计算计算,仅此而已。