Suppose I need to run two concurrent computations, wait for both of them, and then combine their results. More specifically, I need to run f1: X1 => Y1 and <
The problem is that monadic composition implies sequential wait. In our case it implies that we wait for one future first and then we will wait for another.
This is unfortunately true.
import java.util.Date
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object Test extends App {
def timestamp(label: String): Unit = Console.println(label + ": " + new Date().getTime.toString)
timestamp("Start")
for {
step1 <- Future {
Thread.sleep(2000)
timestamp("step1")
}
step2 <- Future {
Thread.sleep(1000)
timestamp("step2")
}
} yield { timestamp("Done") }
Thread.sleep(4000)
}
Running this code outputs:
Start: 1430473518753
step1: 1430473520778
step2: 1430473521780
Done: 1430473521781
Thus it looks like we need an applicative composition of the futures to wait till either both complete or at least one future fails.
I am not sure applicative composition has anything to do with the concurrent strategy. Using for comprehensions, you get a result if all futures complete or a failure if any of them fails. So it's semantically the same.
I think the reason why futures are run sequentially is because step1 is available within step2 (and in the rest of the computation). Essentially we can convert the for block as:
def step1() = Future {
Thread.sleep(2000)
timestamp("step1")
}
def step2() = Future {
Thread.sleep(1000)
timestamp("step2")
}
def finalStep() = timestamp("Done")
step1().flatMap(step1 => step2()).map(finalStep())
So the result of previous computations are available to the rest of the steps. It differs from > & <*> in this respect.
@andrey-tyukin's code runs futures in parallel:
import java.util.Date
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
object Test extends App {
def timestamp(label: String): Unit = Console.println(label + ": " + new Date().getTime.toString)
timestamp("Start")
(Future {
Thread.sleep(2000)
timestamp("step1")
} zip Future {
Thread.sleep(1000)
timestamp("step2")
}).map(_ => timestamp("Done"))
Thread.sleep(4000)
}
Output:
Start: 1430474667418
step2: 1430474668444
step1: 1430474669444
Done: 1430474669446