Scala async vs. Java ForkJoinTask

Некоторое время назад я обнаружил 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 не может? Может быть, в случае более сложных сценариев? Что мне не хватает?

Ответ 1

Существует одна важная разница в том, как два отрывка, которые вы опубликовали, работают под капотом: операции блокировки.

Фрагмент scala-async примерно эквивалентен:

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

Это код на основе обратного вызова. Там нет операций, которые блокировали бы любой поток. Только регистрация в будущем и обратный вызов (что происходит при капоте flatMap в этом случае). Другими словами, все там асинхронно.

С другой стороны, метод join из пула fork-join Java блокирует поток.

Никакие операции блокировки не являются значительным преимуществом производительности/масштабируемости, потому что - значительно упрощает - нет блокировки = > меньше потребляемых потоков = > меньше ресурсов ОС или меньше переключения контекста.

Подведение итогов: целью scala-async является сделать неблокируемую, основанную на обратном вызове асинхронную обработку как естественную в своем синтаксисе стандартного подхода блокировки (например, тот, который вы использовали в Java).