Запуск scala Futures одновременно для понимания без будущих переменных в локальной области видимости - PullRequest
0 голосов
/ 24 апреля 2020

Предположим, у меня есть будущий результат, назовем его garfield

def garfield = Future{
  Thread.sleep(100)
  System.currentTimeMillis()
}

Я могу запустить garfield одновременно с for comprehension, как это

val f1 = garfield
val f2 = garfield

for {
  r1 <- f1
  r2 <- f2
} yield r1 -> r2

, как объяснено. в этот ответ.

Предположим, я не хочу загрязнять локальную область видимости будущими переменными, если они мне не понадобятся позже. Является ли это допустимым альтернативным подходом?

for {
  f1 <- Future{garfield}
  f2 <- Future{garfield}
  r1 <- f1
  r2 <- f2
} yield r1 -> r2

Редактировать

Похоже, мой оригинальный подход, использующий Future.apply, включает в себя служебные данные, которые в большинстве случаев вызывают последовательное выполнение, см. пример .

Использование альтернативного подхода

for {
  f1 <- Future.successful(garfield)
  f2 <- Future.successful(garfield)
  r1 <- f1
  r2 <- f2
} yield r1 -> r2

ведет себя, как и ожидалось.

С другой стороны, этот подход немного странный и, возможно, более традиционный подход определения области видимости. Фьючерсы на Unit предпочтительнее.

val res = {
  val f1 = garfield
  val f2 = garfield
  for {
    r1 <- f1
    r2 <- f2
  } yield r1 -> r2
}

Я ... ios, если кто-то может пролить немного света на причину спорадиального c отсутствия одновременного выполнения.

1 Ответ

1 голос
/ 24 апреля 2020

Поскольку понимание принципиально последовательно, нет, это не сработает.

Ваш код будет последовательно оценивать f1, а затем f2.

Следующее должно работать

(Обновлено с некоторыми изменениями из ссылка из @ViktorKlang)

object FutureFor extends App {
  import concurrent.ExecutionContext.Implicits.global

  for {
    _ <- Future.unit
    f1 = Future { "garfield" }
    f2 = Future { "garfield" }
    r1 <- f1
    r2 <- f2
  } yield r1 -> r2

}

Вы должны начать с <-, чтобы использовать из "первоначального" будущего, и он будет определять тип результата для-понимания.

Параллельность будет достигнута с помощью =, поскольку он создаст фьючерсы, а затем потребит их с помощью <-

Но это действительно сбивает с толку, и я бы предложил придерживаться

val f1 = garfield
val f2 = garfield

for {
  r1 <- f1
  r2 <- f2
} yield r1 -> r2

Редактировать:

Ваш подход Future { garfield() } работает, и я упустил момент, что он оборачивает будущее.

И это одновременно. См. Модифицированный код, подтверждающий это:

import java.time.Instant
import java.util.concurrent.Executors

import scala.concurrent.{ExecutionContext, Future}
import scala.util.Random

object FutureFor extends App {
  private val random = new Random()
  implicit val ex =
    ExecutionContext.fromExecutor(Executors.newFixedThreadPool(10))

  def garfield() = Future {
    val started = Instant.now()
    Thread.sleep(random.nextInt(1000))
    val stopped = Instant.now()
    s"Started:$started on ${Thread.currentThread().getName}. Stopped $stopped"
  }

  val bar = Future
    .sequence {
      for {
        _ <- 1 to 10
      } yield
        for {
          f1 <- Future { garfield() }
          f2 <- Future { garfield() }
          r1 <- f1
          r2 <- f2
        } yield r1 + "\n" + r2
    }
    .map(_.mkString("\n\n"))
    .foreach(println)

  Thread.sleep(5000)
}

Отпечатки:

Started:2020-04-24T13:23:46.043230Z on pool-1-thread-3. Stopped 2020-04-24T13:23:46.889296Z
Started:2020-04-24T13:23:46.428162Z on pool-1-thread-10. Stopped 2020-04-24T13:23:47.159586Z
....
...