Reputation: 4292
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
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
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