CervEd
CervEd

Reputation: 4292

Running scala Futures conncurrently in for comprehension without future variables in local scope

Suppose I have a future result, let's call it garfield

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

I can run garfield concurrently with in for comprehension like this

val f1 = garfield
val f2 = garfield

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

as explained in this answer.

Suppose I don't want to polluting the local scope with future variables if I won't need them later. Is this a valid alternative approach?

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

Edit

It appears my original approach, using Future.apply includes overhead that most of the time causes sequential execution, see example.

Using the alternative approach

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

behaves as expected.

Then again, this approach is a bit odd and perhaps a more conventional approach of scoping the futures in a Unit is preferred.

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

I'm curios if someone could shed some more light on the reason for the sporadic lack of concurrent execution.

Upvotes: 0

Views: 189

Answers (1)

Ivan Stanislavciuc
Ivan Stanislavciuc

Reputation: 7275

For comprehension is sequential in principal, so no, this won't work.

Your code will sequentially evaluate f1 and then f2.

The following should work

(Updated with some changes from link from @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

}

You have to start with <- to consume from "initial" future and it will decide the outcome type of for-comprehension.

The concurrency would be achieved with = as it will create Futures and then consume them with <-

But this is really confusing and I'd suggest to stick to

val f1 = garfield
val f2 = garfield

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

Edit:

Your approach Future { garfield() } does work and I missed the point that it is wrapping a Future.

And it is concurrent. See modified code that proves it:

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)
}

Prints:

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
....

Upvotes: 1

Related Questions