【问题标题】:Scala async vs. Java ForkJoinTaskScala 异步与 Java ForkJoinTask
【发布时间】:2024-04-25 04:25:02
【问题描述】:

前段时间我发现了Scala Async Project。问题是:这个 async 块有什么神奇之处,不能通过普通函数(没有宏扩展)实现?

让我们看一下介绍中的第一个例子:

import ExecutionContext.Implicits.global
import scala.async.Async.{async, await}

val future = async {
    val f1 = async { ...; true }
    val f2 = async { ...; 42 }
    if (await(f1)) await(f2) else 0
}

在上面的示例中,我没有看到任何不能用纯 Java 编写的内容。这段代码做同样的事情:

import java.util.concurrent.*;
import java.util.function.Supplier;

// First define a helper method for creating async blocks:
public static <T> ForkJoinTask<T> async(Supplier<T> supplier) {
    return new RecursiveTask<T>() {
        @Override
        protected T compute() {
            return supplier.get();
        }
    }.fork();
}

ForkJoinTask<Integer> future = ForkJoinPool.commonPool().submit(() -> {
    ForkJoinTask<Boolean> f1 = async(() -> true);
    ForkJoinTask<Integer> f2 = async(() -> 42);

    if (f1.join()) {
        return f2.join();
    } else {
        return 42;
    }
});

Scala async 能做什么而 Java 不能?也许在一些更复杂的情况下?我错过了什么?

【问题讨论】:

  • 我认为 Scala 中的“任何东西”都可以重写为纯 Java .. 只要有足够的时间和精力:|
  • @user2864740 和任何用 Java 编写的东西都可以在纯汇编器中重新实现 :-)
  • 你觉得哪个代码更简洁?
  • 不行,async除了语法之外肯定有杀手锏。我想到的一个想法是async 可以在任何线程池上工作,而不需要ForkJoinPool

标签: java scala asynchronous async-await forkjoinpool


【解决方案1】:

您发布的两个 sn-ps 如何在后台工作有一个关键区别:阻塞操作

scala-asyncsn-p 大致相当于:

val future = {
  val f1 = Future { ...; true }
  val f2 = Future { ...; 42 }
  f1.flatMap { b =>
    if(b) f2 else Future.successful(0)
  }
}

这是一个基于回调的代码。那里没有会阻塞任何线程的操作。只有未来的包装和回调注册(在这种情况下发生在 flatMap 的引擎盖下)。换句话说,那里的一切都是异步的

另一方面,Java 的 fork-join 池中的 join 方法确实阻塞了线程。

无阻塞操作是显着的性能/可扩展性优势,因为 - 大大简化 - 无阻塞 => 需要更少的线程 => 需要更少的操作系统资源 + 更少的上下文切换。

总结:scala-async 的目的是使非阻塞、基于回调的 异步 处理在语法上与标准的阻塞方法一样自然(就像您在 Java 中使用的那样) .

【讨论】:

  • @njzk2 不,它没有,这就是 scala-async 所做的所有宏观魔术的全部意义。
  • await 怎么可能不真正等待答案呢?即它如何根据计算所述值的线程尚未计算的值继续执行?
  • @njzk2 await 仅在“语法上”等待 - 它看起来像一个阻塞调用,而实际上是非阻塞的。 真正发生了什么,有一个“延续”回调正在注册。这种转换发生在编译时(awaitasync,而不是实际的方法调用)。
  • @njzk2 查看the sources - 它明确指出await 调用由await 宏转换为onComplete 调用。
  • 那么这意味着该方法的其余部分要么在新线程上执行,要么在我们等待的异步使用的线程上执行?