При использовании фьючерсов Scala привязаны ли обратные вызовы с одним и тем же контекстом выполнения к синхронным вызовам?

Новое будущее в Scala 2.10 использует контекст выполнения для каждой операции, где действие называется асинхронно (включая map, filter и т.д.). Означает ли это, что каждое действие всегда будет вызываться индивидуально через контекст выполнения или возможно, что этот шаг оптимизируется при объединении нескольких преобразований/фильтров, каждый из которых использует один и тот же контекст выполнения?

т.е. если делать f.map(...).filter(...).map(...), все с одним и тем же контекстом выполнения, будет ли этот вызов execute() один раз (потому что он достаточно умен, чтобы создать синхронную функцию из выше) или три раза?

Если будущее Scala не выполняет вышеуказанную оптимизацию, существует ли альтернативная структура, более подходящая для композиций с длинными цепочками, которые делают выше?

Ответы

Ответ 1

Я не могу предоставить ссылку на документацию, в которой будет четко указано, что действительно произойдет, но мы можем провести простой эксперимент, который ответит на ваш вопрос.

Просто откройте Scala REPL и вставьте следующий код:

import java.util.concurrent.Executors
import scala.concurrent._

implicit val ec = new ExecutionContext {
    val threadPool = Executors.newFixedThreadPool(1000);

    def execute(runnable: Runnable) {
        threadPool.submit(runnable)
        println("execute!")
    }

    def reportFailure(t: Throwable) {}
}

future { 1 } map(_ + 1) filter (_ > 0) map (_ + 2) 

Он будет печатать:

scala > future {1} map (_ + 1) фильтр (_ > 0) map (_ + 2)
выполнить!
выполнить!
выполнить!
выполнить!
res0: scala.concurrent.Future [Int] = scala.concurrent.impl.Promise $DefaultPromise @7ef3de76

Так выполняется вызов для каждой отдельной операции, которую вы выполняете (и, как вы можете проверить документацию, каждая функция, такая как карта или фильтр, принимает ExecutionContext как неявный параметр: http://www.scala-lang.org/api/2.10.6/#scala.concurrent.Future)

Если вы ищете альтернативную структуру, вы должны проверить фразу "Scalaз". У меня нет опыта с ними, но они кажутся тем, что вы ищете. Проверьте эту тему: https://groups.google.com/forum/#!msg/scalaz/-PuakIf-g_4/7ydrU5VIfDQJ

В отличие от реализации Future в Scala 2.10, map и flatMap НЕ запускают новые задачи и не требуют неявного ExecutionContext. Вместо этого map и flatMap просто добавляют к текущему (трассируемому) продолжению, которое будет выполняться потоком "current", если явно не разветвлено через Future.fork или Future.apply. Это означает, что Future обеспечивает гораздо лучшее повторное использование потоков, чем реализация 2.10, и избегает ненужных циклов представления пула потоков.

Future также отличается от типа Scala 2.10 Future тем, что он не обязательно представляет собой текущее вычисление. Вместо этого мы снова вводим недетерминизм с использованием функций интерфейса scalaz.Nondeterminsm. Это упрощает нашу реализацию и упрощает анализ кода, поскольку порядок эффектов и точки недетерминированности становятся полностью явными и не зависят от порядка оценки Scala.

ВАЖНОЕ ЗАМЕЧАНИЕ: Future не включает обработку ошибок и обычно используется только как строительный блок для библиотек, которые хотят использовать возможности Future, но хотят разработать собственную стратегию обработки ошибок. См. scalaz.concurrent.Task для типа, который расширяет Future с правильной обработкой ошибок - это всего лишь оболочка для Future[Either[Throwable,A]] с рядом дополнительных функций удобства.